design-constraint-validator 1.1.0 → 2.1.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 (99) hide show
  1. package/README.md +90 -21
  2. package/adapters/decisionthemes.d.ts +44 -0
  3. package/adapters/decisionthemes.d.ts.map +1 -0
  4. package/adapters/decisionthemes.js +35 -0
  5. package/adapters/decisionthemes.ts +59 -0
  6. package/cli/commands/build.js +1 -1
  7. package/cli/commands/build.ts +1 -1
  8. package/cli/commands/graph.js +4 -4
  9. package/cli/commands/graph.ts +4 -4
  10. package/cli/commands/validate.d.ts.map +1 -1
  11. package/cli/commands/validate.js +43 -6
  12. package/cli/commands/validate.ts +41 -8
  13. package/cli/config-schema.d.ts +39 -0
  14. package/cli/config-schema.d.ts.map +1 -1
  15. package/cli/config-schema.js +4 -2
  16. package/cli/config-schema.ts +4 -2
  17. package/cli/config.d.ts.map +1 -1
  18. package/cli/config.js +8 -2
  19. package/cli/config.ts +8 -2
  20. package/cli/constraint-registry.d.ts +16 -0
  21. package/cli/constraint-registry.d.ts.map +1 -1
  22. package/cli/constraint-registry.js +64 -31
  23. package/cli/constraint-registry.ts +67 -31
  24. package/cli/dcv.js +8 -24
  25. package/cli/dcv.ts +8 -20
  26. package/cli/json-output.d.ts +8 -1
  27. package/cli/json-output.d.ts.map +1 -1
  28. package/cli/json-output.js +13 -4
  29. package/cli/json-output.ts +20 -4
  30. package/cli/types.d.ts +2 -0
  31. package/cli/types.d.ts.map +1 -1
  32. package/cli/types.ts +2 -0
  33. package/cli/validate-api.d.ts +40 -0
  34. package/cli/validate-api.d.ts.map +1 -0
  35. package/cli/validate-api.js +85 -0
  36. package/cli/validate-api.ts +126 -0
  37. package/cli/version-banner.d.ts +20 -0
  38. package/cli/version-banner.d.ts.map +1 -0
  39. package/cli/version-banner.js +49 -0
  40. package/cli/version-banner.ts +61 -0
  41. package/core/breakpoints.d.ts +8 -2
  42. package/core/breakpoints.d.ts.map +1 -1
  43. package/core/breakpoints.js +24 -3
  44. package/core/breakpoints.ts +22 -3
  45. package/core/color.js +4 -4
  46. package/core/color.ts +4 -4
  47. package/core/constraints/monotonic-lightness.d.ts.map +1 -1
  48. package/core/constraints/monotonic-lightness.js +9 -5
  49. package/core/constraints/monotonic-lightness.ts +9 -4
  50. package/core/constraints/wcag.d.ts.map +1 -1
  51. package/core/constraints/wcag.js +6 -0
  52. package/core/constraints/wcag.ts +6 -0
  53. package/core/dtcg.d.ts +38 -0
  54. package/core/dtcg.d.ts.map +1 -0
  55. package/core/dtcg.js +88 -0
  56. package/core/dtcg.ts +102 -0
  57. package/core/engine.d.ts +6 -0
  58. package/core/engine.d.ts.map +1 -1
  59. package/core/engine.ts +7 -0
  60. package/core/flatten.d.ts +5 -3
  61. package/core/flatten.d.ts.map +1 -1
  62. package/core/flatten.js +24 -10
  63. package/core/flatten.ts +39 -16
  64. package/core/image-export.d.ts.map +1 -1
  65. package/core/image-export.js +10 -7
  66. package/core/image-export.ts +9 -6
  67. package/core/index.d.ts +2 -0
  68. package/core/index.d.ts.map +1 -1
  69. package/core/index.js +4 -0
  70. package/core/index.ts +6 -0
  71. package/core/why.d.ts +1 -1
  72. package/core/why.d.ts.map +1 -1
  73. package/core/why.ts +1 -1
  74. package/mcp/contracts.d.ts +118 -0
  75. package/mcp/contracts.d.ts.map +1 -0
  76. package/mcp/contracts.js +30 -0
  77. package/mcp/contracts.ts +51 -0
  78. package/mcp/index.d.ts +9 -0
  79. package/mcp/index.d.ts.map +1 -0
  80. package/mcp/index.js +32 -0
  81. package/mcp/index.ts +70 -0
  82. package/mcp/tools.d.ts +52 -0
  83. package/mcp/tools.d.ts.map +1 -0
  84. package/mcp/tools.js +172 -0
  85. package/mcp/tools.ts +254 -0
  86. package/package.json +41 -26
  87. package/server.json +21 -0
  88. package/cli/constraints-loader.d.ts +0 -30
  89. package/cli/constraints-loader.d.ts.map +0 -1
  90. package/cli/constraints-loader.js +0 -58
  91. package/cli/constraints-loader.ts +0 -83
  92. package/cli/engine-helpers.d.ts +0 -41
  93. package/cli/engine-helpers.d.ts.map +0 -1
  94. package/cli/engine-helpers.js +0 -135
  95. package/cli/engine-helpers.ts +0 -133
  96. package/core/cross-axis-config.d.ts +0 -34
  97. package/core/cross-axis-config.d.ts.map +0 -1
  98. package/core/cross-axis-config.js +0 -173
  99. package/core/cross-axis-config.ts +0 -182
package/mcp/tools.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { z } from 'zod';
3
+ import { type WhyReport } from '../core/why.js';
4
+ import { type ValidateResult } from '../cli/validate-api.js';
5
+ import type { ValidateToolInput, WhyToolInput, GraphToolInput } from './contracts.js';
6
+ export type DcvMcpToolName = 'validate' | 'why' | 'graph';
7
+ export interface ToolFailure {
8
+ ok: false;
9
+ tool: DcvMcpToolName;
10
+ error: {
11
+ code: string;
12
+ message: string;
13
+ details?: Record<string, unknown>;
14
+ };
15
+ }
16
+ export type ToolResponse<T extends {
17
+ ok: boolean;
18
+ } & object> = ({
19
+ tool: DcvMcpToolName;
20
+ } & T) | ToolFailure;
21
+ export declare class ToolExecutionError extends Error {
22
+ readonly code: string;
23
+ readonly details?: Record<string, unknown>;
24
+ constructor(code: string, message: string, details?: Record<string, unknown>);
25
+ }
26
+ export interface GraphToolResult {
27
+ ok: true;
28
+ nodes: string[];
29
+ edges: Array<[string, string]>;
30
+ meta: {
31
+ nodeCount: number;
32
+ edgeCount: number;
33
+ };
34
+ }
35
+ export type WhyToolResult = {
36
+ ok: true;
37
+ } & WhyReport;
38
+ interface ToolDefinition<TInput, TResult extends {
39
+ ok: boolean;
40
+ } & object> {
41
+ name: DcvMcpToolName;
42
+ description: string;
43
+ inputSchema: Record<string, z.ZodTypeAny>;
44
+ handler: (input: TInput) => Promise<ToolResponse<TResult>> | ToolResponse<TResult>;
45
+ }
46
+ export declare function validateTool(input: ValidateToolInput): Promise<ToolResponse<ValidateResult>>;
47
+ export declare function whyTool(input: WhyToolInput): Promise<ToolResponse<WhyToolResult>>;
48
+ export declare function graphTool(input: GraphToolInput): Promise<ToolResponse<GraphToolResult>>;
49
+ export declare const dcvMcpTools: Array<ToolDefinition<ValidateToolInput, ValidateResult> | ToolDefinition<WhyToolInput, WhyToolResult> | ToolDefinition<GraphToolInput, GraphToolResult>>;
50
+ export declare function registerDcvMcpTools(server: McpServer): void;
51
+ export {};
52
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAI7B,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGvE,OAAO,KAAK,EAAc,iBAAiB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGlG,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,KAAK,GAAG,OAAO,CAAC;AAE1D,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACnC,CAAC;CACH;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,IAAI,CAAC;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC;AAE5G,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAE/B,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAK7E;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/B,IAAI,EAAE;QACJ,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,SAAS,CAAC;AAWrD,UAAU,cAAc,CAAC,MAAM,EAAE,OAAO,SAAS;IAAE,EAAE,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM;IACvE,IAAI,EAAE,cAAc,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;IAC1C,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;CACpF;AAyFD,wBAAsB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC,CAelG;AAED,wBAAsB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAevF;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC,CAe7F;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAC3B,cAAc,CAAC,iBAAiB,EAAE,cAAc,CAAC,GACjD,cAAc,CAAC,YAAY,EAAE,aAAa,CAAC,GAC3C,cAAc,CAAC,cAAc,EAAE,eAAe,CAAC,CAoBlD,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAuB3D"}
package/mcp/tools.js ADDED
@@ -0,0 +1,172 @@
1
+ import fs from 'node:fs';
2
+ import { suggestIds } from '../core/cli-format.js';
3
+ import { flattenTokens } from '../core/flatten.js';
4
+ import { explain } from '../core/why.js';
5
+ import { validate } from '../cli/validate-api.js';
6
+ import { graphInputShape, validateInputShape, whyInputShape } from './contracts.js';
7
+ export class ToolExecutionError extends Error {
8
+ code;
9
+ details;
10
+ constructor(code, message, details) {
11
+ super(message);
12
+ this.code = code;
13
+ this.details = details;
14
+ }
15
+ }
16
+ function errorMessage(error) {
17
+ return error instanceof Error ? error.message : 'Unknown tool failure';
18
+ }
19
+ function toFailure(tool, error) {
20
+ if (error instanceof ToolExecutionError) {
21
+ return {
22
+ ok: false,
23
+ tool,
24
+ error: {
25
+ code: error.code,
26
+ message: error.message,
27
+ ...(error.details ? { details: error.details } : {}),
28
+ },
29
+ };
30
+ }
31
+ return {
32
+ ok: false,
33
+ tool,
34
+ error: {
35
+ code: 'tool_execution_failed',
36
+ message: errorMessage(error),
37
+ },
38
+ };
39
+ }
40
+ async function executeTool(tool, fn) {
41
+ try {
42
+ return {
43
+ tool,
44
+ ...(await fn()),
45
+ };
46
+ }
47
+ catch (error) {
48
+ return toFailure(tool, error);
49
+ }
50
+ }
51
+ function isToolFailure(result) {
52
+ return result.ok === false && 'error' in result;
53
+ }
54
+ function responseToContent(result) {
55
+ return JSON.stringify(result, null, 2);
56
+ }
57
+ function parseJsonFile(filePath) {
58
+ if (!fs.existsSync(filePath)) {
59
+ throw new ToolExecutionError('tokens_not_found', `Tokens file not found: ${filePath}`);
60
+ }
61
+ try {
62
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
63
+ }
64
+ catch (error) {
65
+ throw new ToolExecutionError('invalid_tokens', `Tokens file is not valid JSON: ${filePath}`, {
66
+ cause: errorMessage(error),
67
+ });
68
+ }
69
+ }
70
+ function asJsonObject(value, label) {
71
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
72
+ return value;
73
+ }
74
+ throw new ToolExecutionError('invalid_input', `${label} must be a JSON object.`);
75
+ }
76
+ function resolveTokens(input) {
77
+ if (input.tokens !== undefined) {
78
+ return input.tokens;
79
+ }
80
+ if (input.tokensPath !== undefined) {
81
+ return asJsonObject(parseJsonFile(input.tokensPath), 'tokensPath contents');
82
+ }
83
+ throw new ToolExecutionError('invalid_input', 'Provide either tokens or tokensPath.');
84
+ }
85
+ function constraints(input) {
86
+ return input.constraints;
87
+ }
88
+ export async function validateTool(input) {
89
+ return executeTool('validate', () => {
90
+ if (input.tokens === undefined && input.tokensPath === undefined) {
91
+ throw new ToolExecutionError('invalid_input', 'Provide either tokens or tokensPath.');
92
+ }
93
+ return validate({
94
+ ...(input.tokens !== undefined ? { tokens: input.tokens } : {}),
95
+ ...(input.tokens === undefined && input.tokensPath !== undefined ? { tokensPath: input.tokensPath } : {}),
96
+ ...(input.constraints !== undefined ? { constraints: constraints(input) } : {}),
97
+ ...(input.configPath !== undefined ? { configPath: input.configPath } : {}),
98
+ ...(input.constraintsDir !== undefined ? { constraintsDir: input.constraintsDir } : {}),
99
+ ...(input.breakpoint !== undefined ? { breakpoint: input.breakpoint } : {}),
100
+ });
101
+ });
102
+ }
103
+ export async function whyTool(input) {
104
+ return executeTool('why', () => {
105
+ const { flat, edges } = flattenTokens(resolveTokens(input));
106
+ if (!Object.prototype.hasOwnProperty.call(flat, input.tokenId)) {
107
+ throw new ToolExecutionError('unknown_token', `Unknown token id: ${input.tokenId}`, {
108
+ tokenId: input.tokenId,
109
+ suggestions: suggestIds(input.tokenId, Object.keys(flat)).map((suggestion) => suggestion.id),
110
+ });
111
+ }
112
+ return {
113
+ ok: true,
114
+ ...explain(input.tokenId, flat, edges),
115
+ };
116
+ });
117
+ }
118
+ export async function graphTool(input) {
119
+ return executeTool('graph', () => {
120
+ const { flat, edges } = flattenTokens(resolveTokens(input));
121
+ const nodes = Object.keys(flat).sort();
122
+ return {
123
+ ok: true,
124
+ nodes,
125
+ edges,
126
+ meta: {
127
+ nodeCount: nodes.length,
128
+ edgeCount: edges.length,
129
+ },
130
+ };
131
+ });
132
+ }
133
+ export const dcvMcpTools = [
134
+ {
135
+ name: 'validate',
136
+ description: 'Validate DTCG-style design tokens against DCV constraints and return structured violations.',
137
+ inputSchema: validateInputShape,
138
+ handler: validateTool,
139
+ },
140
+ {
141
+ name: 'why',
142
+ description: 'Explain token provenance, aliases, immediate dependencies, dependents, and alias chain.',
143
+ inputSchema: whyInputShape,
144
+ handler: whyTool,
145
+ },
146
+ {
147
+ name: 'graph',
148
+ description: 'Return the token dependency graph as nodes and directed edges.',
149
+ inputSchema: graphInputShape,
150
+ handler: graphTool,
151
+ },
152
+ ];
153
+ export function registerDcvMcpTools(server) {
154
+ for (const tool of dcvMcpTools) {
155
+ server.registerTool(tool.name, {
156
+ description: tool.description,
157
+ inputSchema: tool.inputSchema,
158
+ }, async (input) => {
159
+ const result = await tool.handler(input);
160
+ return {
161
+ content: [
162
+ {
163
+ type: 'text',
164
+ text: responseToContent(result),
165
+ },
166
+ ],
167
+ structuredContent: result,
168
+ ...(isToolFailure(result) ? { isError: true } : {}),
169
+ };
170
+ });
171
+ }
172
+ }
package/mcp/tools.ts ADDED
@@ -0,0 +1,254 @@
1
+ import fs from 'node:fs';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
4
+ import type { z } from 'zod';
5
+
6
+ import { suggestIds } from '../core/cli-format.js';
7
+ import { flattenTokens, type TokenNode } from '../core/flatten.js';
8
+ import { explain, type WhyReport } from '../core/why.js';
9
+ import { validate, type ValidateResult } from '../cli/validate-api.js';
10
+ import type { DcvConfig } from '../cli/types.js';
11
+ import type { Breakpoint } from '../core/breakpoints.js';
12
+ import type { JsonObject, ValidateToolInput, WhyToolInput, GraphToolInput } from './contracts.js';
13
+ import { graphInputShape, validateInputShape, whyInputShape } from './contracts.js';
14
+
15
+ export type DcvMcpToolName = 'validate' | 'why' | 'graph';
16
+
17
+ export interface ToolFailure {
18
+ ok: false;
19
+ tool: DcvMcpToolName;
20
+ error: {
21
+ code: string;
22
+ message: string;
23
+ details?: Record<string, unknown>;
24
+ };
25
+ }
26
+
27
+ export type ToolResponse<T extends { ok: boolean } & object> = ({ tool: DcvMcpToolName } & T) | ToolFailure;
28
+
29
+ export class ToolExecutionError extends Error {
30
+ readonly code: string;
31
+ readonly details?: Record<string, unknown>;
32
+
33
+ constructor(code: string, message: string, details?: Record<string, unknown>) {
34
+ super(message);
35
+ this.code = code;
36
+ this.details = details;
37
+ }
38
+ }
39
+
40
+ export interface GraphToolResult {
41
+ ok: true;
42
+ nodes: string[];
43
+ edges: Array<[string, string]>;
44
+ meta: {
45
+ nodeCount: number;
46
+ edgeCount: number;
47
+ };
48
+ }
49
+
50
+ export type WhyToolResult = { ok: true } & WhyReport;
51
+
52
+ interface TokenInput {
53
+ tokens?: JsonObject;
54
+ tokensPath?: string;
55
+ constraints?: JsonObject;
56
+ configPath?: string;
57
+ constraintsDir?: string;
58
+ breakpoint?: Breakpoint;
59
+ }
60
+
61
+ interface ToolDefinition<TInput, TResult extends { ok: boolean } & object> {
62
+ name: DcvMcpToolName;
63
+ description: string;
64
+ inputSchema: Record<string, z.ZodTypeAny>;
65
+ handler: (input: TInput) => Promise<ToolResponse<TResult>> | ToolResponse<TResult>;
66
+ }
67
+
68
+ function errorMessage(error: unknown): string {
69
+ return error instanceof Error ? error.message : 'Unknown tool failure';
70
+ }
71
+
72
+ function toFailure(tool: DcvMcpToolName, error: unknown): ToolFailure {
73
+ if (error instanceof ToolExecutionError) {
74
+ return {
75
+ ok: false,
76
+ tool,
77
+ error: {
78
+ code: error.code,
79
+ message: error.message,
80
+ ...(error.details ? { details: error.details } : {}),
81
+ },
82
+ };
83
+ }
84
+
85
+ return {
86
+ ok: false,
87
+ tool,
88
+ error: {
89
+ code: 'tool_execution_failed',
90
+ message: errorMessage(error),
91
+ },
92
+ };
93
+ }
94
+
95
+ async function executeTool<T extends { ok: boolean } & object>(
96
+ tool: DcvMcpToolName,
97
+ fn: () => Promise<T> | T,
98
+ ): Promise<ToolResponse<T>> {
99
+ try {
100
+ return {
101
+ tool,
102
+ ...(await fn()),
103
+ };
104
+ } catch (error) {
105
+ return toFailure(tool, error);
106
+ }
107
+ }
108
+
109
+ function isToolFailure(result: ToolResponse<{ ok: boolean }>): result is ToolFailure {
110
+ return result.ok === false && 'error' in result;
111
+ }
112
+
113
+ function responseToContent(result: ToolResponse<{ ok: boolean }>): string {
114
+ return JSON.stringify(result, null, 2);
115
+ }
116
+
117
+ function parseJsonFile(filePath: string): unknown {
118
+ if (!fs.existsSync(filePath)) {
119
+ throw new ToolExecutionError('tokens_not_found', `Tokens file not found: ${filePath}`);
120
+ }
121
+
122
+ try {
123
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
124
+ } catch (error) {
125
+ throw new ToolExecutionError('invalid_tokens', `Tokens file is not valid JSON: ${filePath}`, {
126
+ cause: errorMessage(error),
127
+ });
128
+ }
129
+ }
130
+
131
+ function asJsonObject(value: unknown, label: string): JsonObject {
132
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
133
+ return value as JsonObject;
134
+ }
135
+
136
+ throw new ToolExecutionError('invalid_input', `${label} must be a JSON object.`);
137
+ }
138
+
139
+ function resolveTokens(input: TokenInput): TokenNode {
140
+ if (input.tokens !== undefined) {
141
+ return input.tokens as unknown as TokenNode;
142
+ }
143
+
144
+ if (input.tokensPath !== undefined) {
145
+ return asJsonObject(parseJsonFile(input.tokensPath), 'tokensPath contents') as unknown as TokenNode;
146
+ }
147
+
148
+ throw new ToolExecutionError('invalid_input', 'Provide either tokens or tokensPath.');
149
+ }
150
+
151
+ function constraints(input: TokenInput): DcvConfig['constraints'] | undefined {
152
+ return input.constraints as DcvConfig['constraints'] | undefined;
153
+ }
154
+
155
+ export async function validateTool(input: ValidateToolInput): Promise<ToolResponse<ValidateResult>> {
156
+ return executeTool('validate', () => {
157
+ if (input.tokens === undefined && input.tokensPath === undefined) {
158
+ throw new ToolExecutionError('invalid_input', 'Provide either tokens or tokensPath.');
159
+ }
160
+
161
+ return validate({
162
+ ...(input.tokens !== undefined ? { tokens: input.tokens as unknown as TokenNode } : {}),
163
+ ...(input.tokens === undefined && input.tokensPath !== undefined ? { tokensPath: input.tokensPath } : {}),
164
+ ...(input.constraints !== undefined ? { constraints: constraints(input) } : {}),
165
+ ...(input.configPath !== undefined ? { configPath: input.configPath } : {}),
166
+ ...(input.constraintsDir !== undefined ? { constraintsDir: input.constraintsDir } : {}),
167
+ ...(input.breakpoint !== undefined ? { breakpoint: input.breakpoint } : {}),
168
+ });
169
+ });
170
+ }
171
+
172
+ export async function whyTool(input: WhyToolInput): Promise<ToolResponse<WhyToolResult>> {
173
+ return executeTool('why', () => {
174
+ const { flat, edges } = flattenTokens(resolveTokens(input));
175
+ if (!Object.prototype.hasOwnProperty.call(flat, input.tokenId)) {
176
+ throw new ToolExecutionError('unknown_token', `Unknown token id: ${input.tokenId}`, {
177
+ tokenId: input.tokenId,
178
+ suggestions: suggestIds(input.tokenId, Object.keys(flat)).map((suggestion) => suggestion.id),
179
+ });
180
+ }
181
+
182
+ return {
183
+ ok: true as const,
184
+ ...explain(input.tokenId, flat, edges),
185
+ };
186
+ });
187
+ }
188
+
189
+ export async function graphTool(input: GraphToolInput): Promise<ToolResponse<GraphToolResult>> {
190
+ return executeTool('graph', () => {
191
+ const { flat, edges } = flattenTokens(resolveTokens(input));
192
+ const nodes = Object.keys(flat).sort();
193
+
194
+ return {
195
+ ok: true as const,
196
+ nodes,
197
+ edges,
198
+ meta: {
199
+ nodeCount: nodes.length,
200
+ edgeCount: edges.length,
201
+ },
202
+ };
203
+ });
204
+ }
205
+
206
+ export const dcvMcpTools: Array<
207
+ | ToolDefinition<ValidateToolInput, ValidateResult>
208
+ | ToolDefinition<WhyToolInput, WhyToolResult>
209
+ | ToolDefinition<GraphToolInput, GraphToolResult>
210
+ > = [
211
+ {
212
+ name: 'validate',
213
+ description: 'Validate DTCG-style design tokens against DCV constraints and return structured violations.',
214
+ inputSchema: validateInputShape,
215
+ handler: validateTool,
216
+ },
217
+ {
218
+ name: 'why',
219
+ description: 'Explain token provenance, aliases, immediate dependencies, dependents, and alias chain.',
220
+ inputSchema: whyInputShape,
221
+ handler: whyTool,
222
+ },
223
+ {
224
+ name: 'graph',
225
+ description: 'Return the token dependency graph as nodes and directed edges.',
226
+ inputSchema: graphInputShape,
227
+ handler: graphTool,
228
+ },
229
+ ];
230
+
231
+ export function registerDcvMcpTools(server: McpServer): void {
232
+ for (const tool of dcvMcpTools) {
233
+ server.registerTool(
234
+ tool.name,
235
+ {
236
+ description: tool.description,
237
+ inputSchema: tool.inputSchema,
238
+ },
239
+ async (input): Promise<CallToolResult> => {
240
+ const result = await tool.handler(input as never);
241
+ return {
242
+ content: [
243
+ {
244
+ type: 'text',
245
+ text: responseToContent(result as ToolResponse<{ ok: boolean }>),
246
+ },
247
+ ],
248
+ structuredContent: result as unknown as Record<string, unknown>,
249
+ ...(isToolFailure(result as ToolResponse<{ ok: boolean }>) ? { isError: true } : {}),
250
+ };
251
+ },
252
+ );
253
+ }
254
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "design-constraint-validator",
3
- "version": "1.1.0",
3
+ "version": "2.1.0",
4
4
  "description": "Mathematical constraint validator for design systems — ensuring consistency, accessibility, and logical coherence",
5
5
  "type": "module",
6
6
  "engines": {
@@ -14,30 +14,42 @@
14
14
  "import": "./core/index.js"
15
15
  },
16
16
  "./core/*": "./core/*",
17
- "./cli/*": "./cli/*"
17
+ "./cli/*": "./cli/*",
18
+ "./mcp": {
19
+ "types": "./mcp/index.d.ts",
20
+ "import": "./mcp/index.js"
21
+ },
22
+ "./mcp/*": "./mcp/*"
18
23
  },
19
24
  "bin": {
20
25
  "dcv": "./cli/index.js",
21
- "design-constraint-validator": "./cli/index.js"
26
+ "design-constraint-validator": "./cli/index.js",
27
+ "dcv-mcp": "./mcp/index.js"
22
28
  },
29
+ "mcpName": "io.github.cseperkepapp/design-constraint-validator",
23
30
  "scripts": {
24
- "test": "vitest run",
25
- "test:watch": "vitest",
31
+ "test": "vitest run --exclude \"**/*.test.js\"",
32
+ "test:watch": "vitest --exclude \"**/*.test.js\"",
26
33
  "typecheck": "tsc --noEmit",
34
+ "workflow:typecheck": "tsc -p tsconfig.workflow-automation.json --noEmit",
35
+ "validate-headers": "tsx scripts/validate-headers.ts",
36
+ "workflow:test": "vitest run src/__tests__/task-workflow-integrity.test.ts src/__tests__/sync-task-index.test.ts",
37
+ "task:sync": "tsx scripts/sync-task-index.ts",
38
+ "rename-done-tasks": "tsx scripts/rename-done-tasks.ts",
27
39
  "build": "tsc",
28
40
  "lint": "eslint . --ext .js,.ts,.mjs,.cjs",
29
41
  "format": "prettier -w .",
30
- "check": "npm run typecheck && npm run lint && npm test",
31
- "prepublishOnly": "npm run check && npm run build",
42
+ "check": "npm run typecheck && npm run lint && npm run build && npm test",
43
+ "prepublishOnly": "npm run check",
32
44
  "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
33
45
  "release:notes": "npm run changelog && git add CHANGELOG.md && git commit -m \"docs(changelog): update\"",
34
- "release:patch": "npm version patch && git push && git push --tags && echo '\n⚠️ Now run: npm publish'",
35
- "release:minor": "npm version minor && git push && git push --tags && echo '\n⚠️ Now run: npm publish'",
36
- "release:major": "npm version major && git push && git push --tags && echo '\n⚠️ Now run: npm publish'",
37
- "demo": "cd ../designLab-DEMO && npm run dev",
38
- "demo:build": "cd ../designLab-DEMO && npm run build",
39
- "demo:v2": "cd designLab-v2 && npm run dev",
40
- "demo:v2:build": "cd designLab-v2 && npm run build"
46
+ "release:patch": "npm version patch && git push && git push --tags && echo '\n Tag pushed. CI (.github/workflows/publish.yml) will build, publish, and verify the version on npm — no manual npm publish needed. Watch the Actions run.'",
47
+ "release:minor": "npm version minor && git push && git push --tags && echo '\n Tag pushed. CI (.github/workflows/publish.yml) will build, publish, and verify the version on npm — no manual npm publish needed. Watch the Actions run.'",
48
+ "release:major": "npm version major && git push && git push --tags && echo '\n Tag pushed. CI (.github/workflows/publish.yml) will build, publish, and verify the version on npm — no manual npm publish needed. Watch the Actions run.'",
49
+ "demo": "node -e \"const fs=require('node:fs');const cp=require('node:child_process');const dir='../designLab-DEMO';if(!fs.existsSync(dir)){console.error('Demo repo not found at ../designLab-DEMO. Clone it next to this repo to run npm run demo.');process.exit(1)}const r=cp.spawnSync('npm',['run','dev'],{cwd:dir,stdio:'inherit',shell:true});process.exit(r.status===null?1:r.status)\"",
50
+ "demo:build": "node -e \"const fs=require('node:fs');const cp=require('node:child_process');const dir='../designLab-DEMO';if(!fs.existsSync(dir)){console.error('Demo repo not found at ../designLab-DEMO. Clone it next to this repo to run npm run demo:build.');process.exit(1)}const r=cp.spawnSync('npm',['run','build'],{cwd:dir,stdio:'inherit',shell:true});process.exit(r.status===null?1:r.status)\"",
51
+ "demo:v2": "node -e \"const fs=require('node:fs');const cp=require('node:child_process');const dir='designLab-v2';if(!fs.existsSync(dir)){console.error('Demo repo not found at designLab-v2. Add it inside this repo to run npm run demo:v2.');process.exit(1)}const r=cp.spawnSync('npm',['run','dev'],{cwd:dir,stdio:'inherit',shell:true});process.exit(r.status===null?1:r.status)\"",
52
+ "demo:v2:build": "node -e \"const fs=require('node:fs');const cp=require('node:child_process');const dir='designLab-v2';if(!fs.existsSync(dir)){console.error('Demo repo not found at designLab-v2. Add it inside this repo to run npm run demo:v2:build.');process.exit(1)}const r=cp.spawnSync('npm',['run','build'],{cwd:dir,stdio:'inherit',shell:true});process.exit(r.status===null?1:r.status)\""
41
53
  },
42
54
  "keywords": [
43
55
  "design-constraints",
@@ -67,29 +79,32 @@
67
79
  "access": "public"
68
80
  },
69
81
  "dependencies": {
70
- "fast-glob": "^3.3.2",
71
- "yargs": "^17.0.0",
72
- "zod": "^3.0.0"
82
+ "@modelcontextprotocol/sdk": "^1.29.0",
83
+ "yargs": "^17.7.2",
84
+ "zod": "^3.25.76"
73
85
  },
74
86
  "devDependencies": {
75
- "@types/node": "^22.0.0",
76
- "@types/yargs": "^17.0.0",
77
- "@typescript-eslint/eslint-plugin": "^8.0.0",
78
- "@typescript-eslint/parser": "^8.0.0",
87
+ "@types/node": "^22.19.21",
88
+ "@types/yargs": "^17.0.35",
89
+ "@typescript-eslint/eslint-plugin": "^8.61.0",
90
+ "@typescript-eslint/parser": "^8.61.0",
79
91
  "conventional-changelog-cli": "^2.2.2",
80
- "eslint": "^9.0.0",
92
+ "eslint": "^9.39.4",
81
93
  "eslint-config-prettier": "^9.0.0",
94
+ "glob": "^11.1.0",
82
95
  "pdf-parse": "^2.4.5",
83
- "prettier": "^3.0.0",
84
- "tsx": "^4.0.0",
85
- "typescript": "^5.0.0",
86
- "vitest": "^3.0.0"
96
+ "prettier": "^3.8.4",
97
+ "tsx": "^4.22.4",
98
+ "typescript": "^5.9.3",
99
+ "vitest": "^3.2.6"
87
100
  },
88
101
  "files": [
89
102
  "cli/",
90
103
  "core/",
104
+ "mcp/",
91
105
  "adapters/",
92
106
  "themes/",
107
+ "server.json",
93
108
  "LICENSE",
94
109
  "README.md"
95
110
  ]
package/server.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.cseperkepapp/design-constraint-validator",
4
+ "title": "Design Constraint Validator",
5
+ "description": "Validate design tokens for accessibility, scales, and design-system constraint consistency.",
6
+ "version": "2.1.0",
7
+ "repository": {
8
+ "url": "https://github.com/CseperkePapp/design-constraint-validator",
9
+ "source": "github"
10
+ },
11
+ "packages": [
12
+ {
13
+ "registryType": "npm",
14
+ "identifier": "design-constraint-validator",
15
+ "version": "2.1.0",
16
+ "transport": {
17
+ "type": "stdio"
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -1,30 +0,0 @@
1
- /**
2
- * @deprecated This module is deprecated. Use constraint-registry.ts instead.
3
- *
4
- * Phase 3A (Architectural Cleanup): This file contains legacy runtime constraint
5
- * loading logic that has been replaced by the centralized constraint-registry.ts module.
6
- *
7
- * Migration: Replace attachRuntimeConstraints() with setupConstraints() from constraint-registry.ts
8
- *
9
- * This file will be removed in a future major version.
10
- */
11
- import type { Engine } from '../core/engine.js';
12
- import type { Breakpoint } from '../core/breakpoints.js';
13
- import type { DcvConfig } from './types.js';
14
- type AttachRuntimeOpts = {
15
- config: DcvConfig;
16
- knownIds: Set<string>;
17
- bp?: Breakpoint;
18
- crossAxisDebug?: boolean;
19
- };
20
- /**
21
- * Attach runtime constraints that depend on project files or built-in policies:
22
- * - Cross-axis rules from themes/cross-axis*.rules.json
23
- * - Built-in threshold rules (e.g., control.size.min >= 44px)
24
- *
25
- * @deprecated Use setupConstraints() from constraint-registry.ts instead.
26
- * This function will be removed in a future major version.
27
- */
28
- export declare function attachRuntimeConstraints(engine: Engine, opts: AttachRuntimeOpts): void;
29
- export {};
30
- //# sourceMappingURL=constraints-loader.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constraints-loader.d.ts","sourceRoot":"","sources":["constraints-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,KAAK,iBAAiB,GAAG;IACvB,MAAM,EAAE,SAAS,CAAC;IAClB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,EAAE,CAAC,EAAE,UAAU,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAkDtF"}