jui-tools-mcp-server 2.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.
Files changed (172) hide show
  1. package/.github/workflows/test.yml +35 -0
  2. package/README.md +148 -0
  3. package/data/attribute_definitions.json +4248 -0
  4. package/data/attribute_semantics.json +137 -0
  5. package/data/binding_semantics.json +115 -0
  6. package/data/component_metadata.json +769 -0
  7. package/data/coverage.json +114 -0
  8. package/data/screen_identity.json +350 -0
  9. package/dist/cli_runner.d.ts +10 -0
  10. package/dist/cli_runner.js +33 -0
  11. package/dist/config.d.ts +19 -0
  12. package/dist/config.js +67 -0
  13. package/dist/data/component_metadata.d.ts +30 -0
  14. package/dist/data/component_metadata.js +498 -0
  15. package/dist/data/derived.d.ts +198 -0
  16. package/dist/data/derived.js +293 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +130 -0
  19. package/dist/spec_loader.d.ts +110 -0
  20. package/dist/spec_loader.js +445 -0
  21. package/dist/tools/api/list_api_models.d.ts +10 -0
  22. package/dist/tools/api/list_api_models.js +37 -0
  23. package/dist/tools/api/list_api_specs.d.ts +19 -0
  24. package/dist/tools/api/list_api_specs.js +42 -0
  25. package/dist/tools/api/preview_api_model_sync.d.ts +11 -0
  26. package/dist/tools/api/preview_api_model_sync.js +34 -0
  27. package/dist/tools/context/get_project_config.d.ts +3 -0
  28. package/dist/tools/context/get_project_config.js +20 -0
  29. package/dist/tools/context/list_component_specs.d.ts +3 -0
  30. package/dist/tools/context/list_component_specs.js +35 -0
  31. package/dist/tools/context/list_layouts.d.ts +3 -0
  32. package/dist/tools/context/list_layouts.js +118 -0
  33. package/dist/tools/context/list_screen_specs.d.ts +3 -0
  34. package/dist/tools/context/list_screen_specs.js +37 -0
  35. package/dist/tools/context/read_layout_file.d.ts +3 -0
  36. package/dist/tools/context/read_layout_file.js +25 -0
  37. package/dist/tools/context/read_spec_file.d.ts +3 -0
  38. package/dist/tools/context/read_spec_file.js +27 -0
  39. package/dist/tools/doc/doc_generate_component.d.ts +3 -0
  40. package/dist/tools/doc/doc_generate_component.js +38 -0
  41. package/dist/tools/doc/doc_generate_html.d.ts +3 -0
  42. package/dist/tools/doc/doc_generate_html.js +46 -0
  43. package/dist/tools/doc/doc_generate_spec.d.ts +3 -0
  44. package/dist/tools/doc/doc_generate_spec.js +38 -0
  45. package/dist/tools/doc/doc_init_component.d.ts +3 -0
  46. package/dist/tools/doc/doc_init_component.js +30 -0
  47. package/dist/tools/doc/doc_init_spec.d.ts +3 -0
  48. package/dist/tools/doc/doc_init_spec.js +33 -0
  49. package/dist/tools/doc/doc_rules_init.d.ts +3 -0
  50. package/dist/tools/doc/doc_rules_init.js +25 -0
  51. package/dist/tools/doc/doc_rules_show.d.ts +3 -0
  52. package/dist/tools/doc/doc_rules_show.js +21 -0
  53. package/dist/tools/doc/doc_validate_component.d.ts +3 -0
  54. package/dist/tools/doc/doc_validate_component.js +39 -0
  55. package/dist/tools/doc/doc_validate_spec.d.ts +3 -0
  56. package/dist/tools/doc/doc_validate_spec.js +40 -0
  57. package/dist/tools/jui/jui_build.d.ts +3 -0
  58. package/dist/tools/jui/jui_build.js +31 -0
  59. package/dist/tools/jui/jui_generate_converter.d.ts +3 -0
  60. package/dist/tools/jui/jui_generate_converter.js +43 -0
  61. package/dist/tools/jui/jui_generate_project.d.ts +3 -0
  62. package/dist/tools/jui/jui_generate_project.js +43 -0
  63. package/dist/tools/jui/jui_generate_screen.d.ts +3 -0
  64. package/dist/tools/jui/jui_generate_screen.js +22 -0
  65. package/dist/tools/jui/jui_init.d.ts +3 -0
  66. package/dist/tools/jui/jui_init.js +42 -0
  67. package/dist/tools/jui/jui_migrate_layouts.d.ts +3 -0
  68. package/dist/tools/jui/jui_migrate_layouts.js +25 -0
  69. package/dist/tools/jui/jui_sync_tool.d.ts +3 -0
  70. package/dist/tools/jui/jui_sync_tool.js +33 -0
  71. package/dist/tools/jui/jui_verify.d.ts +3 -0
  72. package/dist/tools/jui/jui_verify.js +43 -0
  73. package/dist/tools/spec/get_binding_rules.d.ts +3 -0
  74. package/dist/tools/spec/get_binding_rules.js +8 -0
  75. package/dist/tools/spec/get_data_source.d.ts +3 -0
  76. package/dist/tools/spec/get_data_source.js +41 -0
  77. package/dist/tools/spec/get_modifier_order.d.ts +3 -0
  78. package/dist/tools/spec/get_modifier_order.js +14 -0
  79. package/dist/tools/spec/get_platform_mapping.d.ts +3 -0
  80. package/dist/tools/spec/get_platform_mapping.js +20 -0
  81. package/dist/tools/spec/get_screen_identity.d.ts +3 -0
  82. package/dist/tools/spec/get_screen_identity.js +22 -0
  83. package/dist/tools/spec/lookup_attribute.d.ts +3 -0
  84. package/dist/tools/spec/lookup_attribute.js +14 -0
  85. package/dist/tools/spec/lookup_component.d.ts +3 -0
  86. package/dist/tools/spec/lookup_component.js +15 -0
  87. package/dist/tools/spec/search_components.d.ts +3 -0
  88. package/dist/tools/spec/search_components.js +14 -0
  89. package/dist/tools/test/test_artifacts_pull.d.ts +3 -0
  90. package/dist/tools/test/test_artifacts_pull.js +48 -0
  91. package/dist/tools/test/test_artifacts_status.d.ts +3 -0
  92. package/dist/tools/test/test_artifacts_status.js +28 -0
  93. package/dist/tools/test/test_generate_description.d.ts +3 -0
  94. package/dist/tools/test/test_generate_description.js +27 -0
  95. package/dist/tools/test/test_generate_flow.d.ts +3 -0
  96. package/dist/tools/test/test_generate_flow.js +29 -0
  97. package/dist/tools/test/test_generate_screen.d.ts +3 -0
  98. package/dist/tools/test/test_generate_screen.js +29 -0
  99. package/dist/tools/test/test_mock_generate.d.ts +3 -0
  100. package/dist/tools/test/test_mock_generate.js +64 -0
  101. package/dist/tools/test/test_report.d.ts +3 -0
  102. package/dist/tools/test/test_report.js +26 -0
  103. package/dist/tools/test/test_validate.d.ts +3 -0
  104. package/dist/tools/test/test_validate.js +43 -0
  105. package/docs/design.md +227 -0
  106. package/install.sh +90 -0
  107. package/package.json +24 -0
  108. package/scripts/fetch-definitions.js +101 -0
  109. package/scripts/fetch-manifest.js +60 -0
  110. package/src/cli_runner.ts +55 -0
  111. package/src/config.ts +88 -0
  112. package/src/data/derived.ts +305 -0
  113. package/src/index.ts +157 -0
  114. package/src/spec_loader.ts +591 -0
  115. package/src/tools/api/list_api_models.ts +44 -0
  116. package/src/tools/api/list_api_specs.ts +53 -0
  117. package/src/tools/api/preview_api_model_sync.ts +41 -0
  118. package/src/tools/context/get_project_config.ts +27 -0
  119. package/src/tools/context/list_component_specs.ts +44 -0
  120. package/src/tools/context/list_layouts.ts +131 -0
  121. package/src/tools/context/list_screen_specs.ts +46 -0
  122. package/src/tools/context/read_layout_file.ts +35 -0
  123. package/src/tools/context/read_spec_file.ts +38 -0
  124. package/src/tools/doc/doc_generate_component.ts +43 -0
  125. package/src/tools/doc/doc_generate_html.ts +40 -0
  126. package/src/tools/doc/doc_generate_spec.ts +43 -0
  127. package/src/tools/doc/doc_init_component.ts +32 -0
  128. package/src/tools/doc/doc_init_spec.ts +37 -0
  129. package/src/tools/doc/doc_rules_init.ts +29 -0
  130. package/src/tools/doc/doc_rules_show.ts +27 -0
  131. package/src/tools/doc/doc_validate_component.ts +47 -0
  132. package/src/tools/doc/doc_validate_spec.ts +48 -0
  133. package/src/tools/jui/jui_build.ts +31 -0
  134. package/src/tools/jui/jui_generate_converter.ts +41 -0
  135. package/src/tools/jui/jui_generate_project.ts +37 -0
  136. package/src/tools/jui/jui_generate_screen.ts +28 -0
  137. package/src/tools/jui/jui_init.ts +38 -0
  138. package/src/tools/jui/jui_migrate_layouts.ts +29 -0
  139. package/src/tools/jui/jui_sync_tool.ts +33 -0
  140. package/src/tools/jui/jui_verify.ts +43 -0
  141. package/src/tools/spec/get_binding_rules.ts +16 -0
  142. package/src/tools/spec/get_data_source.ts +53 -0
  143. package/src/tools/spec/get_modifier_order.ts +22 -0
  144. package/src/tools/spec/get_platform_mapping.ts +28 -0
  145. package/src/tools/spec/get_screen_identity.ts +31 -0
  146. package/src/tools/spec/lookup_attribute.ts +22 -0
  147. package/src/tools/spec/lookup_component.ts +23 -0
  148. package/src/tools/spec/search_components.ts +22 -0
  149. package/src/tools/test/test_artifacts_pull.ts +47 -0
  150. package/src/tools/test/test_artifacts_status.ts +34 -0
  151. package/src/tools/test/test_generate_description.ts +33 -0
  152. package/src/tools/test/test_generate_flow.ts +33 -0
  153. package/src/tools/test/test_generate_screen.ts +33 -0
  154. package/src/tools/test/test_mock_generate.ts +58 -0
  155. package/src/tools/test/test_report.ts +32 -0
  156. package/src/tools/test/test_validate.ts +43 -0
  157. package/tests/__snapshots__/derived.test.ts.snap +279 -0
  158. package/tests/bundled_snapshot.test.ts +155 -0
  159. package/tests/cli_runner.test.ts +120 -0
  160. package/tests/config.test.ts +212 -0
  161. package/tests/derived.test.ts +114 -0
  162. package/tests/helpers.ts +272 -0
  163. package/tests/setup.ts +27 -0
  164. package/tests/snapshot_completeness.test.ts +152 -0
  165. package/tests/spec_loader.test.ts +488 -0
  166. package/tests/tools_cli.test.ts +687 -0
  167. package/tests/tools_context.test.ts +274 -0
  168. package/tests/tools_spec.test.ts +225 -0
  169. package/tests/tools_test.test.ts +284 -0
  170. package/tsconfig.json +15 -0
  171. package/uninstall.sh +46 -0
  172. package/vitest.config.ts +15 -0
@@ -0,0 +1,47 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { join } from "path";
4
+ import { ServerConfig } from "../../config.js";
5
+ import { runCli } from "../../cli_runner.js";
6
+
7
+ export function register(server: McpServer, config: ServerConfig) {
8
+ server.tool(
9
+ "doc_validate_component",
10
+ "Validate a component specification JSON file against the schema",
11
+ {
12
+ file: z.string().describe("Component spec file path (e.g., 'my_card.component.json' or full path)"),
13
+ project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
14
+ },
15
+ async (params) => {
16
+ try {
17
+ const projectDir = config.resolveProjectDir(params.project_dir);
18
+ let filePath = params.file;
19
+
20
+ if (!filePath.startsWith("/") && !filePath.includes("/")) {
21
+ try {
22
+ const projectConfig = config.readProjectConfig(projectDir);
23
+ const compDir = config.resolveDir(projectConfig, "component_spec_directory", projectDir);
24
+ filePath = join(compDir, filePath);
25
+ } catch {
26
+ filePath = join(projectDir, filePath);
27
+ }
28
+ }
29
+
30
+ const result = await runCli("jsonui-doc", ["validate", "component", filePath], { cwd: projectDir });
31
+ return {
32
+ content: [{
33
+ type: "text",
34
+ text: JSON.stringify({
35
+ success: result.exitCode === 0,
36
+ is_valid: result.exitCode === 0,
37
+ output: result.stdout,
38
+ errors: result.stderr || undefined,
39
+ }, null, 2),
40
+ }],
41
+ };
42
+ } catch (e: any) {
43
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
44
+ }
45
+ }
46
+ );
47
+ }
@@ -0,0 +1,48 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { join } from "path";
4
+ import { ServerConfig } from "../../config.js";
5
+ import { runCli } from "../../cli_runner.js";
6
+
7
+ export function register(server: McpServer, config: ServerConfig) {
8
+ server.tool(
9
+ "doc_validate_spec",
10
+ "Validate a screen specification JSON file against the schema",
11
+ {
12
+ file: z.string().describe("Spec file path (e.g., 'login.spec.json' or full path)"),
13
+ project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
14
+ },
15
+ async (params) => {
16
+ try {
17
+ const projectDir = config.resolveProjectDir(params.project_dir);
18
+ let filePath = params.file;
19
+
20
+ // If relative and doesn't contain directory separator, resolve from spec_directory
21
+ if (!filePath.startsWith("/") && !filePath.includes("/")) {
22
+ try {
23
+ const projectConfig = config.readProjectConfig(projectDir);
24
+ const specDir = config.resolveDir(projectConfig, "spec_directory", projectDir);
25
+ filePath = join(specDir, filePath);
26
+ } catch {
27
+ filePath = join(projectDir, filePath);
28
+ }
29
+ }
30
+
31
+ const result = await runCli("jsonui-doc", ["validate", "spec", filePath], { cwd: projectDir });
32
+ return {
33
+ content: [{
34
+ type: "text",
35
+ text: JSON.stringify({
36
+ success: result.exitCode === 0,
37
+ is_valid: result.exitCode === 0,
38
+ output: result.stdout,
39
+ errors: result.stderr || undefined,
40
+ }, null, 2),
41
+ }],
42
+ };
43
+ } catch (e: any) {
44
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
45
+ }
46
+ }
47
+ );
48
+ }
@@ -0,0 +1,31 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { ServerConfig } from "../../config.js";
4
+ import { runCli, formatResult } from "../../cli_runner.js";
5
+
6
+ export function register(server: McpServer, config: ServerConfig) {
7
+ server.tool(
8
+ "jui_build",
9
+ "Distribute layouts/strings/images to all platforms and run platform builds (sjui/kjui/rjui build)",
10
+ {
11
+ clean: z.boolean().optional().describe("Clean build (regenerate all)"),
12
+ platform: z.enum(["ios", "android", "web"]).optional().describe("Build single platform only"),
13
+ project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
14
+ },
15
+ async (params) => {
16
+ try {
17
+ const projectDir = config.resolveProjectDir(params.project_dir);
18
+ const args = ["build"];
19
+ if (params.clean) { args.push("--clean"); }
20
+ if (params.platform === "ios") { args.push("--ios-only"); }
21
+ if (params.platform === "android") { args.push("--android-only"); }
22
+ if (params.platform === "web") { args.push("--web-only"); }
23
+
24
+ const result = await runCli("jui", args, { cwd: projectDir, timeout: 300_000 });
25
+ return { content: [{ type: "text", text: formatResult(result) }] };
26
+ } catch (e: any) {
27
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
28
+ }
29
+ }
30
+ );
31
+ }
@@ -0,0 +1,41 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { ServerConfig } from "../../config.js";
4
+ import { runCli, formatResult } from "../../cli_runner.js";
5
+
6
+ export function register(server: McpServer, config: ServerConfig) {
7
+ server.tool(
8
+ "jui_generate_converter",
9
+ "Generate custom component converter code for each platform",
10
+ {
11
+ name: z.string().optional().describe("Component name (direct mode, e.g., 'MyComponent')"),
12
+ from_spec: z.string().optional().describe("Generate from component spec file path"),
13
+ all: z.boolean().optional().describe("Generate from all component specs"),
14
+ attributes: z.string().optional().describe("Attributes in key:type format (e.g., 'prop1:String,prop2:Int')"),
15
+ container: z.boolean().optional().describe("Mark as container component"),
16
+ skip_existing: z.boolean().optional().describe(
17
+ "Leave existing converter files untouched (no overwrite prompt). " +
18
+ "`jui build` enables this automatically; pass it here for idempotent " +
19
+ "explicit scaffold runs."
20
+ ),
21
+ project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
22
+ },
23
+ async (params) => {
24
+ try {
25
+ const projectDir = config.resolveProjectDir(params.project_dir);
26
+ const args = ["generate", "converter"];
27
+ if (params.name) { args.push(params.name); }
28
+ if (params.from_spec) { args.push("--from", params.from_spec); }
29
+ if (params.all) { args.push("--all"); }
30
+ if (params.attributes) { args.push("--attributes", params.attributes); }
31
+ if (params.container) { args.push("--container"); }
32
+ if (params.skip_existing) { args.push("--skip-existing"); }
33
+
34
+ const result = await runCli("jui", args, { cwd: projectDir });
35
+ return { content: [{ type: "text", text: formatResult(result) }] };
36
+ } catch (e: any) {
37
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
38
+ }
39
+ }
40
+ );
41
+ }
@@ -0,0 +1,37 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { ServerConfig } from "../../config.js";
4
+ import { runCli, formatResult } from "../../cli_runner.js";
5
+
6
+ export function register(server: McpServer, config: ServerConfig) {
7
+ server.tool(
8
+ "jui_generate_project",
9
+ "Generate Layout JSON and ViewModel files from screen specs (all or single spec)",
10
+ {
11
+ spec_file: z.string().optional().describe("Single spec file to process (e.g., 'login.spec.json')"),
12
+ force: z.boolean().optional().describe("Force overwrite declaration files"),
13
+ skip_layout: z.boolean().optional().describe("Skip Layout JSON generation"),
14
+ dry_run: z.boolean().optional().describe("Show what would be generated without writing"),
15
+ platform: z.enum(["ios", "android", "web"]).optional().describe("Generate for single platform only"),
16
+ project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
17
+ },
18
+ async (params) => {
19
+ try {
20
+ const projectDir = config.resolveProjectDir(params.project_dir);
21
+ const args = ["generate", "project"];
22
+ if (params.spec_file) { args.push("--file", params.spec_file); }
23
+ if (params.force) { args.push("--force"); }
24
+ if (params.skip_layout) { args.push("--skip-layout"); }
25
+ if (params.dry_run) { args.push("--dry-run"); }
26
+ if (params.platform === "ios") { args.push("--ios-only"); }
27
+ if (params.platform === "android") { args.push("--android-only"); }
28
+ if (params.platform === "web") { args.push("--web-only"); }
29
+
30
+ const result = await runCli("jui", args, { cwd: projectDir, timeout: 120_000 });
31
+ return { content: [{ type: "text", text: formatResult(result) }] };
32
+ } catch (e: any) {
33
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
34
+ }
35
+ }
36
+ );
37
+ }
@@ -0,0 +1,28 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { ServerConfig } from "../../config.js";
4
+ import { runCli, formatResult } from "../../cli_runner.js";
5
+
6
+ export function register(server: McpServer, config: ServerConfig) {
7
+ server.tool(
8
+ "jui_generate_screen",
9
+ "Create screen spec template files for new screens",
10
+ {
11
+ names: z.array(z.string()).describe("Screen names in PascalCase (e.g., ['LoginScreen', 'RegisterScreen'])"),
12
+ display_name: z.string().optional().describe("Display name (for single screen only)"),
13
+ project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
14
+ },
15
+ async (params) => {
16
+ try {
17
+ const projectDir = config.resolveProjectDir(params.project_dir);
18
+ const args = ["generate", "screen", ...params.names];
19
+ if (params.display_name) { args.push("--display-name", params.display_name); }
20
+
21
+ const result = await runCli("jui", args, { cwd: projectDir });
22
+ return { content: [{ type: "text", text: formatResult(result) }] };
23
+ } catch (e: any) {
24
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
25
+ }
26
+ }
27
+ );
28
+ }
@@ -0,0 +1,38 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { ServerConfig } from "../../config.js";
4
+ import { runCli, formatResult } from "../../cli_runner.js";
5
+
6
+ export function register(server: McpServer, config: ServerConfig) {
7
+ server.tool(
8
+ "jui_init",
9
+ "Initialize a new JsonUI project (creates jui.config.json and directory structure)",
10
+ {
11
+ project_name: z.string().describe("Project name"),
12
+ ios_path: z.string().optional().describe("iOS project root relative path"),
13
+ ios_mode: z.enum(["swiftui", "uikit", "all"]).optional().describe("iOS rendering mode (default: swiftui)"),
14
+ android_path: z.string().optional().describe("Android project root relative path"),
15
+ android_mode: z.enum(["compose", "xml"]).optional().describe("Android rendering mode (default: compose)"),
16
+ package_name: z.string().optional().describe("Android package name (e.g., com.example.app)"),
17
+ web_path: z.string().optional().describe("Web project root relative path"),
18
+ project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
19
+ },
20
+ async (params) => {
21
+ try {
22
+ const projectDir = config.resolveProjectDir(params.project_dir);
23
+ const args = ["init", "--project-name", params.project_name];
24
+ if (params.ios_path) { args.push("--ios", params.ios_path); }
25
+ if (params.ios_mode) { args.push("--ios-mode", params.ios_mode); }
26
+ if (params.android_path) { args.push("--android", params.android_path); }
27
+ if (params.android_mode) { args.push("--android-mode", params.android_mode); }
28
+ if (params.package_name) { args.push("--package-name", params.package_name); }
29
+ if (params.web_path) { args.push("--web", params.web_path); }
30
+
31
+ const result = await runCli("jui", args, { cwd: projectDir });
32
+ return { content: [{ type: "text", text: formatResult(result) }] };
33
+ } catch (e: any) {
34
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
35
+ }
36
+ }
37
+ );
38
+ }
@@ -0,0 +1,29 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { ServerConfig } from "../../config.js";
4
+ import { runCli, formatResult } from "../../cli_runner.js";
5
+
6
+ export function register(server: McpServer, config: ServerConfig) {
7
+ server.tool(
8
+ "jui_migrate_layouts",
9
+ "Copy existing platform layouts to the shared layouts directory",
10
+ {
11
+ source_platform: z.enum(["ios", "android", "web"]).optional().describe("Platform to copy from (default: ios)"),
12
+ dry_run: z.boolean().optional().describe("Show what would be copied without executing"),
13
+ project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
14
+ },
15
+ async (params) => {
16
+ try {
17
+ const projectDir = config.resolveProjectDir(params.project_dir);
18
+ const args = ["migrate-layouts"];
19
+ if (params.source_platform) { args.push("--from", params.source_platform); }
20
+ if (params.dry_run) { args.push("--dry-run"); }
21
+
22
+ const result = await runCli("jui", args, { cwd: projectDir });
23
+ return { content: [{ type: "text", text: formatResult(result) }] };
24
+ } catch (e: any) {
25
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
26
+ }
27
+ }
28
+ );
29
+ }
@@ -0,0 +1,33 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { ServerConfig } from "../../config.js";
4
+ import { runCli, formatResult } from "../../cli_runner.js";
5
+
6
+ export function register(server: McpServer, config: ServerConfig) {
7
+ server.tool(
8
+ "jui_sync_tool",
9
+ "Sync the project's platform-local tool checkouts (sjui_tools / kjui_tools / rjui_tools under each platform root) with the home install (~/.jsonui-cli/ or $JSONUI_CLI_PATH). Preserves every `extensions/` directory — that's where custom converters generated by `rjui g converter` etc. live. Also propagates each tool's `.ruby-version` to the platform root so that standalone `rjui build` (not routed through `jui build`) picks up the correct Ruby via rbenv. Safe to run after bumping jsonui-cli or when bootstrapping a newly scaffolded platform (target dir is created on first run).",
10
+ {
11
+ platform: z.enum(["ios", "android", "web"]).optional().describe("Sync only one platform. Defaults to every platform listed in jui.config.json."),
12
+ prune: z.boolean().optional().describe("Delete files in the target that don't exist in the source (never inside extensions/). Default false."),
13
+ dry_run: z.boolean().optional().describe("Show what would change without writing any files."),
14
+ from: z.string().optional().describe("Override source CLI root (default: $JSONUI_CLI_PATH or ~/.jsonui-cli/)."),
15
+ project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
16
+ },
17
+ async (params) => {
18
+ try {
19
+ const projectDir = config.resolveProjectDir(params.project_dir);
20
+ const args = ["sync_tool"];
21
+ if (params.platform) { args.push("--platform", params.platform); }
22
+ if (params.prune) { args.push("--prune"); }
23
+ if (params.dry_run) { args.push("--dry-run"); }
24
+ if (params.from) { args.push("--from", params.from); }
25
+
26
+ const result = await runCli("jui", args, { cwd: projectDir });
27
+ return { content: [{ type: "text", text: formatResult(result) }] };
28
+ } catch (e: any) {
29
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
30
+ }
31
+ }
32
+ );
33
+ }
@@ -0,0 +1,43 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { ServerConfig } from "../../config.js";
4
+ import { runCli } from "../../cli_runner.js";
5
+
6
+ export function register(server: McpServer, config: ServerConfig) {
7
+ server.tool(
8
+ "jui_verify",
9
+ "Compare generated Layout JSON against on-disk layouts and report differences",
10
+ {
11
+ spec_file: z.string().optional().describe("Single spec file to verify (e.g., 'login.spec.json')"),
12
+ detail: z.boolean().optional().describe("Include per-screen diff details in report"),
13
+ fail_on_diff: z.boolean().optional().describe("Exit with error if any diff detected"),
14
+ platform: z.string().optional().describe("Target platform (defaults to first in config)"),
15
+ project_dir: z.string().optional().describe("Project directory (overrides JUI_PROJECT_DIR env)"),
16
+ },
17
+ async (params) => {
18
+ try {
19
+ const projectDir = config.resolveProjectDir(params.project_dir);
20
+ const args = ["verify"];
21
+ if (params.spec_file) { args.push("--file", params.spec_file); }
22
+ if (params.detail) { args.push("--detail"); }
23
+ if (params.fail_on_diff) { args.push("--fail-on-diff"); }
24
+ if (params.platform) { args.push("--platform", params.platform); }
25
+
26
+ const result = await runCli("jui", args, { cwd: projectDir });
27
+ return {
28
+ content: [{
29
+ type: "text",
30
+ text: JSON.stringify({
31
+ success: result.exitCode === 0,
32
+ has_diffs: result.exitCode !== 0,
33
+ report: result.stdout,
34
+ errors: result.stderr || undefined,
35
+ }, null, 2),
36
+ }],
37
+ };
38
+ } catch (e: any) {
39
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
40
+ }
41
+ }
42
+ );
43
+ }
@@ -0,0 +1,16 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { SpecLoader } from "../../spec_loader.js";
3
+
4
+ export function register(server: McpServer, loader: SpecLoader) {
5
+ server.tool(
6
+ "get_binding_rules",
7
+ "Get binding syntax rules: two-way vs read-only, platform-specific formats, critical rules",
8
+ {},
9
+ async () => {
10
+ const result = loader.getBindingRules();
11
+ return {
12
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
13
+ };
14
+ }
15
+ );
16
+ }
@@ -0,0 +1,53 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { SpecLoader } from "../../spec_loader.js";
3
+
4
+ export function register(server: McpServer, loader: SpecLoader) {
5
+ server.tool(
6
+ "get_data_source",
7
+ "Report where the MCP server loaded EVERY canonical data file from — attribute_definitions.json (raw schema), component_metadata.json (presentation metadata), screen_identity.json and binding_semantics.json. Shows layer (env / cwd / home / bundled), the absolute path actually read, file mtime and freshness per file, plus when this server process loaded them. Use it when the CLI and the MCP seem to disagree about the canon: it distinguishes a stale file from a server that simply has not been restarted since the file changed.",
8
+ {},
9
+ async () => {
10
+ const info = loader.getDataSource();
11
+ const files = [
12
+ info.attributeDefinitions,
13
+ info.componentMetadata,
14
+ info.screenIdentity,
15
+ info.bindingSemantics,
16
+ info.attributeSemantics,
17
+ ].filter((f): f is NonNullable<typeof f> => f != null);
18
+ const stale = files.some((f) => f.freshness === "stale");
19
+ // The data is read once at construction and cached in memory, so a
20
+ // file edited after this timestamp is NOT what the server is serving,
21
+ // however current the file on disk looks.
22
+ const loadedAt = loader.getLoadedAt();
23
+ const changedSinceLoad = loader.getChangedSinceLoad();
24
+ return {
25
+ content: [
26
+ {
27
+ type: "text",
28
+ text: JSON.stringify(
29
+ {
30
+ attributeDefinitions: info.attributeDefinitions,
31
+ componentMetadata: info.componentMetadata,
32
+ screenIdentity: info.screenIdentity,
33
+ bindingSemantics: info.bindingSemantics,
34
+ attributeSemantics: info.attributeSemantics,
35
+ componentCount: info.componentCount,
36
+ commonAttributeCount: info.commonAttributeCount,
37
+ loadedAt,
38
+ staleInMemory: changedSinceLoad.length > 0 ? changedSinceLoad : undefined,
39
+ hint: changedSinceLoad.length
40
+ ? "These files changed on disk AFTER this server loaded them. The server is serving the older content from memory — restart it."
41
+ : stale
42
+ ? "At least one data file is > 90 days old. Re-fetch via `npm rebuild jui-tools-mcp-server` or point JSONUI_CLI_PATH at a fresher checkout."
43
+ : undefined,
44
+ },
45
+ null,
46
+ 2
47
+ ),
48
+ },
49
+ ],
50
+ };
51
+ }
52
+ );
53
+ }
@@ -0,0 +1,22 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { SpecLoader } from "../../spec_loader.js";
4
+
5
+ export function register(server: McpServer, loader: SpecLoader) {
6
+ server.tool(
7
+ "get_modifier_order",
8
+ "Get modifier application order for a platform (critical for correct rendering)",
9
+ {
10
+ platform: z
11
+ .enum(["swift", "kotlin", "react"])
12
+ .optional()
13
+ .describe("Platform (omit for all platforms)"),
14
+ },
15
+ async ({ platform }) => {
16
+ const result = loader.getModifierOrder(platform);
17
+ return {
18
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
19
+ };
20
+ }
21
+ );
22
+ }
@@ -0,0 +1,28 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { SpecLoader } from "../../spec_loader.js";
4
+
5
+ export function register(server: McpServer, loader: SpecLoader) {
6
+ server.tool(
7
+ "get_platform_mapping",
8
+ "Get cross-platform attribute value conversion mapping (e.g. matchParent, textAlign, fontWeight)",
9
+ {
10
+ category: z
11
+ .string()
12
+ .optional()
13
+ .describe("Mapping category (values, contentMode, textAlign, fontWeight, orientation, gravity, types). Omit for all."),
14
+ },
15
+ async ({ category }) => {
16
+ const result = loader.getPlatformMapping(category);
17
+ if (!result) {
18
+ const categories = Object.keys(loader.getPlatformMapping()).join(", ");
19
+ return {
20
+ content: [{ type: "text", text: `Category '${category}' not found. Available: ${categories}` }],
21
+ };
22
+ }
23
+ return {
24
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
25
+ };
26
+ }
27
+ );
28
+ }
@@ -0,0 +1,31 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { SpecLoader } from "../../spec_loader.js";
3
+
4
+ export function register(server: McpServer, loader: SpecLoader) {
5
+ server.tool(
6
+ "get_screen_identity",
7
+ "Get the canonical screen-identity rules: what counts as a screen, how a screen id is derived, " +
8
+ "the runtime marker, the per-platform 'is this screen displayed' predicates, and the " +
9
+ "assert:\"screen\" vocabulary. Use before writing screen/flow tests or navigation code.",
10
+ {},
11
+ async () => {
12
+ const result = loader.getScreenIdentity();
13
+ if (!result) {
14
+ return {
15
+ content: [
16
+ {
17
+ type: "text",
18
+ text:
19
+ "screen_identity.json is not available in this installation. " +
20
+ "Update the MCP server (npm run fetch-definitions) or point it at a " +
21
+ "jsonui-cli checkout that has shared/core/screen_identity.json.",
22
+ },
23
+ ],
24
+ };
25
+ }
26
+ return {
27
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
28
+ };
29
+ }
30
+ );
31
+ }
@@ -0,0 +1,22 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { SpecLoader } from "../../spec_loader.js";
4
+
5
+ export function register(server: McpServer, loader: SpecLoader) {
6
+ server.tool(
7
+ "lookup_attribute",
8
+ "Look up a specific attribute definition: type, binding support, which components use it, the declared platform surface (`platform`, with `platform_reasons` explaining excluded platforms), and `implementationGaps` — platforms that declare the attribute but whose codegen does not consume it yet, with the recorded reason",
9
+ { name: z.string().describe("Attribute name (e.g. 'fontSize', 'padding', 'onClick')") },
10
+ async ({ name }) => {
11
+ const result = loader.getAttribute(name);
12
+ if (!result) {
13
+ return {
14
+ content: [{ type: "text", text: `Attribute '${name}' not found.` }],
15
+ };
16
+ }
17
+ return {
18
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
19
+ };
20
+ }
21
+ );
22
+ }
@@ -0,0 +1,23 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { SpecLoader } from "../../spec_loader.js";
4
+
5
+ export function register(server: McpServer, loader: SpecLoader) {
6
+ server.tool(
7
+ "lookup_component",
8
+ "Look up a JsonUI component specification including attributes, binding behavior, platform-specific details, and rules",
9
+ { name: z.string().describe("Component name (e.g. 'TextField', 'Button', 'View')") },
10
+ async ({ name }) => {
11
+ const spec = loader.getComponentWithCommon(name);
12
+ if (!spec) {
13
+ const available = loader.listComponents().join(", ");
14
+ return {
15
+ content: [{ type: "text", text: `Component '${name}' not found. Available: ${available}` }],
16
+ };
17
+ }
18
+ return {
19
+ content: [{ type: "text", text: JSON.stringify(spec, null, 2) }],
20
+ };
21
+ }
22
+ );
23
+ }
@@ -0,0 +1,22 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { SpecLoader } from "../../spec_loader.js";
4
+
5
+ export function register(server: McpServer, loader: SpecLoader) {
6
+ server.tool(
7
+ "search_components",
8
+ "Search components and attributes by keyword (e.g. 'binding', 'focus', 'color')",
9
+ { query: z.string().describe("Search keyword") },
10
+ async ({ query }) => {
11
+ const results = loader.searchComponents(query);
12
+ if (results.length === 0) {
13
+ return {
14
+ content: [{ type: "text", text: `No results for '${query}'.` }],
15
+ };
16
+ }
17
+ return {
18
+ content: [{ type: "text", text: JSON.stringify(results.slice(0, 20), null, 2) }],
19
+ };
20
+ }
21
+ );
22
+ }