@json-to-office/mcp-server 1.4.0 → 1.5.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
@@ -2,6 +2,7 @@ import { McpServer, McpServerFactory, JsonSchemaType, StandardSchemaWithJSON } f
2
2
  import { FormatAdapter, FormatName } from '@json-to-office/jto-ops';
3
3
  export { FormatAdapter, FormatName } from '@json-to-office/jto-ops';
4
4
  import { ValidationError } from '@json-to-office/shared';
5
+ import { QualityCategory, QualityCertainty, QualityEvidence, JsonPatchOperation as JsonPatchOperation$1 } from '@json-to-office/quality';
5
6
 
6
7
  /**
7
8
  * Structured results, not protocol errors.
@@ -32,6 +33,14 @@ interface Diagnostic {
32
33
  suggestion?: string;
33
34
  /** Free-form extras (offending value, component name, renderer id, …). */
34
35
  context?: Record<string, unknown>;
36
+ source?: 'schema' | 'semantic' | 'renderer' | 'quality';
37
+ ruleId?: string;
38
+ category?: QualityCategory;
39
+ certainty?: QualityCertainty;
40
+ relatedPaths?: readonly string[];
41
+ evidence?: QualityEvidence;
42
+ fixes?: readonly JsonPatchOperation$1[];
43
+ blocking?: boolean;
35
44
  }
36
45
  /**
37
46
  * The envelope every tool's `structuredContent` starts from.
@@ -100,6 +109,8 @@ declare const ERROR_CODES: {
100
109
  readonly HOST_NOTE: "W_HOST_NOTE";
101
110
  /** A generation warning the core raised without a code of its own. */
102
111
  readonly GENERATION: "W_GENERATION";
112
+ /** A design-quality rule threw, so its whole class of findings is missing. */
113
+ readonly QUALITY_RULE_ERROR: "W_QUALITY_RULE_ERROR";
103
114
  /** A required host binary (LibreOffice, poppler) is absent. */
104
115
  readonly DEPENDENCY_MISSING: "E_DEPENDENCY_MISSING";
105
116
  /** The client cancelled the request. */
@@ -177,6 +188,10 @@ declare const OPTION_ERROR_CODES: {
177
188
  readonly INVALID_THEME_PATH: "E_INVALID_THEME_PATH";
178
189
  /** The tool does not support the requested format. */
179
190
  readonly UNSUPPORTED_FORMAT: "E_UNSUPPORTED_FORMAT";
191
+ /** `quality.profile` does not cover the format or renderer of this run. */
192
+ readonly INVALID_QUALITY_PROFILE: "E_INVALID_QUALITY_PROFILE";
193
+ /** `quality.policy` sets a gate, severity or budget that is not a legal value. */
194
+ readonly INVALID_QUALITY_POLICY: "E_INVALID_QUALITY_POLICY";
180
195
  };
181
196
  /**
182
197
  * Normalize a validator path to an RFC 6901 JSON Pointer.
@@ -492,7 +507,7 @@ declare function createToolDeps(options?: CreateToolDepsOptions): ToolDeps;
492
507
  * lose more than they fix, and that looking at a rendered page is cheaper than
493
508
  * reasoning about whether a layout worked (#271).
494
509
  */
495
- declare const SERVER_INSTRUCTIONS = "Author Microsoft Word (.docx) and PowerPoint (.pptx) documents as JSON.\n\nThe JSON is authoritative. A generated file is a build product of the document JSON plus a renderer, a theme, fonts, assets and options \u2014 edit the JSON and regenerate; never treat the binary as the source.\n\nWorking rules:\n- Discover before authoring. Call jto_info first, then jto_discover and jto_describe_component (or read the jto:// resources) for the components and renderer ids a format actually supports.\n- Make small edits. With a workspace handle, patch precisely (RFC 6902 over RFC 6901 paths) instead of resending the whole document; without one, change one region at a time.\n- Validate often. Run jto_validate after each edit rather than once at the end; diagnostics are path-addressed, so they map straight back onto the JSON you just changed.\n- Preview when the answer is visual. jto_preview renders pages to PNG; use it whenever layout, overflow or fit is in question, not only before finishing.\n- Snapshot before risky changes. jto_workspace_snapshot pins the current revision so a restructuring you cannot cleanly undo is still recoverable.\n\nDocument defects come back as structured diagnostics with ok: false, not as errors \u2014 read them and repair. Generated files are written under the server's output root and returned as paths; ask for base64 only for small artifacts.";
510
+ declare const SERVER_INSTRUCTIONS = "Author Microsoft Word (.docx) and PowerPoint (.pptx) documents as JSON.\n\nThe JSON is authoritative. A generated file is a build product of the document JSON plus a renderer, a theme, fonts, assets and options \u2014 edit the JSON and regenerate; never treat the binary as the source.\n\nWorking rules:\n- Discover before authoring. Call jto_info first, then jto_discover and jto_describe_component (or read the jto:// resources) for the components and renderer ids a format actually supports.\n- Make small edits. With a workspace handle, patch precisely (RFC 6902 over RFC 6901 paths) instead of resending the whole document; without one, change one region at a time.\n- Validate often. Run jto_validate after each edit rather than once at the end; diagnostics are path-addressed, so they map straight back onto the JSON you just changed.\n- Treat design findings as defects. Schema-valid is not well-designed: jto_validate also lints layout and legibility (W_QUALITY_* \u2014 undeclared slide canvas, text overflowing its box, overcrowded slides, table widths exceeding their section). These never block generation, but they almost always show in the rendered result \u2014 repair them like errors.\n- Preview when the answer is visual. jto_preview renders pages to PNG; use it whenever layout, overflow or fit is in question, not only before finishing.\n- Snapshot before risky changes. jto_workspace_snapshot pins the current revision so a restructuring you cannot cleanly undo is still recoverable.\n\nDocument defects come back as structured diagnostics with ok: false, not as errors \u2014 read them and repair. Generated files are written under the server's output root and returned as paths; ask for base64 only for small artifacts.";
496
511
  /** Build a server with every tool and resource registered. */
497
512
  declare function createServer(deps: ToolDeps): McpServer;
498
513
  /**
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.4.0" : "dev-mode";
5
+ var SERVER_VERSION = true ? "1.5.0" : "dev-mode";
6
6
  var SERVER_NAME = "json-to-office";
7
7
  var PACKAGE_NAME = "@json-to-office/mcp-server";
8
8
 
@@ -216,6 +216,8 @@ var ERROR_CODES = {
216
216
  HOST_NOTE: "W_HOST_NOTE",
217
217
  /** A generation warning the core raised without a code of its own. */
218
218
  GENERATION: "W_GENERATION",
219
+ /** A design-quality rule threw, so its whole class of findings is missing. */
220
+ QUALITY_RULE_ERROR: "W_QUALITY_RULE_ERROR",
219
221
  /** A required host binary (LibreOffice, poppler) is absent. */
220
222
  DEPENDENCY_MISSING: "E_DEPENDENCY_MISSING",
221
223
  /** The client cancelled the request. */
@@ -312,6 +314,25 @@ function toolResult(payload) {
312
314
  };
313
315
  }
314
316
  var HOST_DEPENDENCY_ERRORS = /* @__PURE__ */ new Set([RENDERER_DEPENDENCY_MISSING]);
317
+ function qualityOptionDiagnostic(error) {
318
+ const code = qualityCallerCode(error);
319
+ if (code === void 0 || code === ERROR_CODES.INVALID_DOCUMENT) {
320
+ return void 0;
321
+ }
322
+ return diagnostic(
323
+ code,
324
+ error instanceof Error ? error.message : String(error)
325
+ );
326
+ }
327
+ function qualityCallerCode(error) {
328
+ const code = error?.code;
329
+ if (code === "QUALITY_PROFILE_INCOMPATIBLE")
330
+ return OPTION_ERROR_CODES.INVALID_QUALITY_PROFILE;
331
+ if (code === "QUALITY_POLICY_INVALID")
332
+ return OPTION_ERROR_CODES.INVALID_QUALITY_POLICY;
333
+ if (code === "QUALITY_GATE_FAILED") return ERROR_CODES.INVALID_DOCUMENT;
334
+ return void 0;
335
+ }
315
336
  function stackAllowed() {
316
337
  const flag = process.env.JTO_MCP_DEBUG_STACKS;
317
338
  return flag === "1" || flag === "true";
@@ -362,7 +383,7 @@ async function guarded(body) {
362
383
  return withHostNotes(result, notes);
363
384
  } catch (error) {
364
385
  const message2 = error instanceof Error ? error.message : String(error);
365
- const code = error instanceof Error && HOST_DEPENDENCY_ERRORS.has(error.name) ? ERROR_CODES.DEPENDENCY_MISSING : ERROR_CODES.INTERNAL;
386
+ const code = qualityCallerCode(error) ?? (error instanceof Error && HOST_DEPENDENCY_ERRORS.has(error.name) ? ERROR_CODES.DEPENDENCY_MISSING : ERROR_CODES.INTERNAL);
366
387
  return withHostNotes(
367
388
  failure(code, message2, {
368
389
  context: {
@@ -381,7 +402,11 @@ var OPTION_ERROR_CODES = {
381
402
  /** `themePath` is not a data-only JSON theme path. */
382
403
  INVALID_THEME_PATH: "E_INVALID_THEME_PATH",
383
404
  /** The tool does not support the requested format. */
384
- UNSUPPORTED_FORMAT: "E_UNSUPPORTED_FORMAT"
405
+ UNSUPPORTED_FORMAT: "E_UNSUPPORTED_FORMAT",
406
+ /** `quality.profile` does not cover the format or renderer of this run. */
407
+ INVALID_QUALITY_PROFILE: "E_INVALID_QUALITY_PROFILE",
408
+ /** `quality.policy` sets a gate, severity or budget that is not a legal value. */
409
+ INVALID_QUALITY_POLICY: "E_INVALID_QUALITY_POLICY"
385
410
  };
386
411
  var DEFERRED_TO_COMPILER = /* @__PURE__ */ new Set([
387
412
  ERROR_CODES.UNSUPPORTED_RENDERER_FEATURE
@@ -421,6 +446,34 @@ function validationDiagnostics(errors) {
421
446
  })
422
447
  );
423
448
  }
449
+ function qualityAnalysisDiagnostics(analysis) {
450
+ return analysis.diagnostics.map((diagnostic2) => ({
451
+ source: diagnostic2.source,
452
+ ruleId: diagnostic2.ruleId,
453
+ category: diagnostic2.category,
454
+ certainty: diagnostic2.certainty,
455
+ severity: diagnostic2.severity,
456
+ code: diagnostic2.code,
457
+ message: diagnostic2.message,
458
+ path: diagnostic2.path,
459
+ blocking: diagnostic2.blocking,
460
+ ...diagnostic2.suggestion !== void 0 && {
461
+ suggestion: diagnostic2.suggestion
462
+ },
463
+ ...diagnostic2.context !== void 0 && {
464
+ context: { ...diagnostic2.context }
465
+ },
466
+ ...diagnostic2.relatedPaths !== void 0 && {
467
+ relatedPaths: diagnostic2.relatedPaths
468
+ },
469
+ ...diagnostic2.evidence !== void 0 && {
470
+ evidence: { ...diagnostic2.evidence }
471
+ },
472
+ ...diagnostic2.fixes !== void 0 && {
473
+ fixes: diagnostic2.fixes
474
+ }
475
+ }));
476
+ }
424
477
  function looksLikeValidationErrors(value) {
425
478
  return Array.isArray(value) && value.length > 0 && value.every(
426
479
  (entry) => typeof entry === "object" && entry !== null && typeof entry.message === "string"
@@ -1117,10 +1170,10 @@ var STARTERS = [
1117
1170
  id: "pptx-minimal",
1118
1171
  format: "pptx",
1119
1172
  title: "Minimal presentation",
1120
- description: "The smallest valid .pptx: root, one slide, one title text.",
1173
+ description: "The smallest well-formed .pptx: root with a declared 16:9 canvas, one slide, one title text. The canvas stays: without it the renderer silently falls back to 4:3.",
1121
1174
  document: {
1122
1175
  name: "pptx",
1123
- props: { title: "Untitled deck" },
1176
+ props: { title: "Untitled deck", slideWidth: 13.333, slideHeight: 7.5 },
1124
1177
  children: [
1125
1178
  {
1126
1179
  name: "slide",
@@ -1994,17 +2047,26 @@ function capDiagnostics(diagnostics, limit) {
1994
2047
  if (diagnostics.length <= limit)
1995
2048
  return { kept: diagnostics, truncated: false };
1996
2049
  const ordered = [...diagnostics].sort(
1997
- (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]
2050
+ (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || Number(b.blocking === true) - Number(a.blocking === true)
1998
2051
  );
1999
2052
  return { kept: ordered.slice(0, limit), truncated: true };
2000
2053
  }
2054
+ function ruleErrorDiagnostics(analysis) {
2055
+ return analysis.ruleErrors.map(
2056
+ (entry) => diagnostic(
2057
+ ERROR_CODES.QUALITY_RULE_ERROR,
2058
+ `Quality rule "${entry.ruleId}" failed: ${entry.message}`,
2059
+ { severity: "warning", source: "quality", ruleId: entry.ruleId }
2060
+ )
2061
+ );
2062
+ }
2001
2063
  var DEFAULT_MAX_DIAGNOSTICS = 100;
2002
2064
  function register4(server, deps) {
2003
2065
  server.registerTool(
2004
2066
  "jto_validate",
2005
2067
  {
2006
2068
  title: "Validate a document",
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.",
2069
+ 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. `ok` mirrors generation: schema and semantic errors block; design-quality `W_QUALITY_*` findings advise by default and block only when `quality.policy.gate` requests it. A broken document is a normal result with `ok: false`, never a protocol error.",
2008
2070
  annotations: { readOnlyHint: true, openWorldHint: false },
2009
2071
  inputSchema: S({
2010
2072
  type: "object",
@@ -2020,6 +2082,29 @@ function register4(server, deps) {
2020
2082
  minimum: 1,
2021
2083
  maximum: 1e3,
2022
2084
  description: `Cap on returned diagnostics (default ${DEFAULT_MAX_DIAGNOSTICS}). Errors are kept ahead of warnings when the cap bites.`
2085
+ },
2086
+ quality: {
2087
+ type: "object",
2088
+ description: "Optional design profile plus per-run enforcement policy.",
2089
+ properties: {
2090
+ profile: {
2091
+ type: "object",
2092
+ properties: { id: { type: "string", minLength: 1 } },
2093
+ required: ["id"],
2094
+ additionalProperties: true
2095
+ },
2096
+ policy: {
2097
+ type: "object",
2098
+ properties: {
2099
+ gate: {
2100
+ type: "string",
2101
+ enum: ["none", "error", "warning", "info"]
2102
+ }
2103
+ },
2104
+ additionalProperties: true
2105
+ }
2106
+ },
2107
+ additionalProperties: false
2023
2108
  }
2024
2109
  },
2025
2110
  required: ["format"],
@@ -2050,7 +2135,11 @@ function register4(server, deps) {
2050
2135
  },
2051
2136
  truncated: {
2052
2137
  type: "boolean",
2053
- description: "`diagnostics` was capped by `maxDiagnostics`."
2138
+ description: "`diagnostics` was capped, by `maxDiagnostics` or by the budget the quality policy set."
2139
+ },
2140
+ profileId: {
2141
+ type: "string",
2142
+ description: "The quality profile the design analysis ran under, when one applied."
2054
2143
  }
2055
2144
  })
2056
2145
  )
@@ -2070,24 +2159,52 @@ function register4(server, deps) {
2070
2159
  resolved.document,
2071
2160
  args.renderer
2072
2161
  );
2073
- const all = [
2162
+ let analysis;
2163
+ let qualityOption;
2164
+ if (adapter.analyzeQuality) {
2165
+ try {
2166
+ analysis = await adapter.analyzeQuality(resolved.document, {
2167
+ renderer: args.renderer,
2168
+ quality: args.quality
2169
+ });
2170
+ } catch (error) {
2171
+ const option = result.valid ? void 0 : qualityOptionDiagnostic(error);
2172
+ if (!option) throw error;
2173
+ qualityOption = option;
2174
+ }
2175
+ }
2176
+ const structural = [
2074
2177
  ...validationDiagnostics(result.errors),
2075
- ...unavailable2 ? [unavailable2] : []
2178
+ ...unavailable2 ? [unavailable2] : [],
2179
+ ...qualityOption ? [qualityOption] : []
2180
+ ];
2181
+ const all = [
2182
+ ...structural,
2183
+ ...analysis ? [
2184
+ ...qualityAnalysisDiagnostics(analysis),
2185
+ ...ruleErrorDiagnostics(analysis)
2186
+ ] : []
2076
2187
  ];
2077
2188
  const counts = countDiagnostics(all);
2189
+ const blocked = countDiagnostics(structural).error > 0 || analysis?.blocked === true;
2078
2190
  const { kept, truncated } = capDiagnostics(
2079
2191
  all,
2080
2192
  args.maxDiagnostics ?? DEFAULT_MAX_DIAGNOSTICS
2081
2193
  );
2082
2194
  return {
2083
- ok: counts.error === 0,
2195
+ ok: !blocked,
2084
2196
  diagnostics: kept,
2085
- valid: counts.error === 0,
2197
+ valid: !blocked,
2086
2198
  format: args.format,
2087
2199
  ...args.renderer !== void 0 && { renderer: args.renderer },
2088
2200
  source: sourceSummary(resolved),
2089
2201
  counts,
2090
- truncated
2202
+ // The engine caps its own list under a policy budget, so a report
2203
+ // shortened there would otherwise come back reading complete.
2204
+ truncated: truncated || analysis?.truncated === true,
2205
+ ...analysis?.profileId !== void 0 && {
2206
+ profileId: analysis.profileId
2207
+ }
2091
2208
  };
2092
2209
  })
2093
2210
  )
@@ -5717,6 +5834,7 @@ var RESOURCE_URIS = {
5717
5834
  catalog: "jto://catalog",
5718
5835
  renderers: "jto://renderers",
5719
5836
  themes: "jto://themes",
5837
+ themeValues: "jto://themes/values",
5720
5838
  templates: "jto://templates",
5721
5839
  documentSchema: (format) => `jto://schema/${format}/document`,
5722
5840
  themeSchema: (format) => `jto://schema/${format}/theme`
@@ -5768,7 +5886,7 @@ function register9(server, deps) {
5768
5886
  RESOURCE_URIS.themes,
5769
5887
  {
5770
5888
  title: "Built-in themes",
5771
- description: "Theme names shipped with each format, usable as a document\u2019s props.theme or as the tools\u2019 theme option.",
5889
+ description: "Theme names shipped with each format, usable as a document\u2019s props.theme or as the tools\u2019 theme option. jto://themes/values carries what each name actually looks like.",
5772
5890
  mimeType: JSON_MIME
5773
5891
  },
5774
5892
  async (uri) => {
@@ -5781,6 +5899,33 @@ function register9(server, deps) {
5781
5899
  });
5782
5900
  }
5783
5901
  );
5902
+ server.registerResource(
5903
+ "theme-values",
5904
+ RESOURCE_URIS.themeValues,
5905
+ {
5906
+ title: "Built-in theme values",
5907
+ description: "The palette, fonts, style tables and component defaults behind every built-in theme name \u2014 what a document actually opts into with props.theme. A name alone cannot tell you whether a theme fits the brief; this can.",
5908
+ mimeType: JSON_MIME
5909
+ },
5910
+ async (uri) => {
5911
+ const formats = await Promise.all(
5912
+ FORMAT_NAMES.map(async (format) => {
5913
+ const adapter = deps.getAdapter(format);
5914
+ let themes;
5915
+ try {
5916
+ themes = adapter.getBuiltinThemeValues ? await adapter.getBuiltinThemeValues() : adapter.getBuiltinThemes();
5917
+ } catch {
5918
+ themes = adapter.getBuiltinThemes();
5919
+ }
5920
+ return {
5921
+ format,
5922
+ themes
5923
+ };
5924
+ })
5925
+ );
5926
+ return jsonContents(uri, { formats });
5927
+ }
5928
+ );
5784
5929
  server.registerResource(
5785
5930
  "templates",
5786
5931
  RESOURCE_URIS.templates,
@@ -5829,6 +5974,7 @@ Working rules:
5829
5974
  - Discover before authoring. Call jto_info first, then jto_discover and jto_describe_component (or read the jto:// resources) for the components and renderer ids a format actually supports.
5830
5975
  - Make small edits. With a workspace handle, patch precisely (RFC 6902 over RFC 6901 paths) instead of resending the whole document; without one, change one region at a time.
5831
5976
  - Validate often. Run jto_validate after each edit rather than once at the end; diagnostics are path-addressed, so they map straight back onto the JSON you just changed.
5977
+ - Treat design findings as defects. Schema-valid is not well-designed: jto_validate also lints layout and legibility (W_QUALITY_* \u2014 undeclared slide canvas, text overflowing its box, overcrowded slides, table widths exceeding their section). These never block generation, but they almost always show in the rendered result \u2014 repair them like errors.
5832
5978
  - Preview when the answer is visual. jto_preview renders pages to PNG; use it whenever layout, overflow or fit is in question, not only before finishing.
5833
5979
  - Snapshot before risky changes. jto_workspace_snapshot pins the current revision so a restructuring you cannot cleanly undo is still recoverable.
5834
5980