@starklab/stark-mcp 0.2.0 → 0.3.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/README.md +42 -4
- package/package.json +12 -1
- package/src/adopt/a11yPass.js +397 -0
- package/src/adopt/adoptGate.js +471 -0
- package/src/adopt/adoptScanReport.js +9 -0
- package/src/adopt/componentPropApi.js +200 -0
- package/src/adopt/findingSnippet.js +386 -0
- package/src/adopt/foreignDiscoveryResolver.js +20 -3
- package/src/adopt/foreignScoringResolver.js +163 -22
- package/src/adopt/moduleGraph.js +44 -2
- package/src/adopt/prCheckReport.js +274 -0
- package/src/adopt/propApiResolver.js +2 -2
- package/src/adopt/referenceResolver.js +2 -2
- package/src/adopt/scanRollup.js +192 -0
- package/src/adopt/tailwindResolver.js +6 -1
- package/src/adopt/targetDiscovery.js +31 -3
- package/src/adopt/tokenAliasResolver.js +45 -2
- package/src/adopt/usageRulesResolver.js +299 -14
- package/src/adopt/vecnaMaterializer.js +45 -5
- package/src/adopt/vecnaVerifier.js +20 -9
- package/src/adopt/wrapperResolver.js +3 -3
- package/src/cli.js +343 -8
- package/src/data.js +105 -11
- package/src/server.js +69 -14
- package/src/whisperer.d.ts +106 -0
- package/src/whisperer.js +814 -0
package/src/server.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
1
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
4
|
import { z } from 'zod';
|
|
3
5
|
|
|
@@ -10,12 +12,13 @@ import {
|
|
|
10
12
|
getLayoutSchema,
|
|
11
13
|
validateLayout,
|
|
12
14
|
getGenerationProtocol,
|
|
15
|
+
whispererContext,
|
|
13
16
|
} from './data.js';
|
|
14
17
|
|
|
15
|
-
const json = value => ({ content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] });
|
|
16
|
-
const error = message => ({ content: [{ type: 'text', text: message }], isError: true });
|
|
18
|
+
const json = (value) => ({ content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] });
|
|
19
|
+
const error = (message) => ({ content: [{ type: 'text', text: message }], isError: true });
|
|
17
20
|
|
|
18
|
-
const wrap = fn => async args => {
|
|
21
|
+
const wrap = (fn) => async (args) => {
|
|
19
22
|
try {
|
|
20
23
|
return json(await fn(args));
|
|
21
24
|
} catch (err) {
|
|
@@ -23,8 +26,16 @@ const wrap = fn => async args => {
|
|
|
23
26
|
}
|
|
24
27
|
};
|
|
25
28
|
|
|
29
|
+
// Read rather than written down: a hand-maintained literal here goes stale on
|
|
30
|
+
// the first release nobody remembers to update it in, and no client ever
|
|
31
|
+
// complains loudly enough to notice — it shipped as 0.1.0 alongside the
|
|
32
|
+
// package's own 0.2.0. cli.js already derives SCANNER_VERSION this way.
|
|
33
|
+
const VERSION = JSON.parse(
|
|
34
|
+
readFileSync(new URL('../package.json', import.meta.url), 'utf-8')
|
|
35
|
+
).version;
|
|
36
|
+
|
|
26
37
|
export function createServer() {
|
|
27
|
-
const server = new McpServer({ name: 'stark-design-system', version:
|
|
38
|
+
const server = new McpServer({ name: 'stark-design-system', version: VERSION });
|
|
28
39
|
|
|
29
40
|
server.registerTool(
|
|
30
41
|
'list_components',
|
|
@@ -46,7 +57,9 @@ export function createServer() {
|
|
|
46
57
|
'Get the dos/donts, prop table, and Figma link for one Stark component. ' +
|
|
47
58
|
'Read this before placing the component in a layout.',
|
|
48
59
|
inputSchema: {
|
|
49
|
-
component: z
|
|
60
|
+
component: z
|
|
61
|
+
.string()
|
|
62
|
+
.describe('Component name, e.g. "Button" or "TextInput" (case-insensitive)'),
|
|
50
63
|
},
|
|
51
64
|
},
|
|
52
65
|
wrap(({ component }) => getComponentUsage(component))
|
|
@@ -71,7 +84,9 @@ export function createServer() {
|
|
|
71
84
|
),
|
|
72
85
|
},
|
|
73
86
|
},
|
|
74
|
-
wrap(({ component, platform, includeTokens }) =>
|
|
87
|
+
wrap(({ component, platform, includeTokens }) =>
|
|
88
|
+
getComponentProps(component, platform, includeTokens)
|
|
89
|
+
)
|
|
75
90
|
);
|
|
76
91
|
|
|
77
92
|
server.registerTool(
|
|
@@ -93,7 +108,7 @@ export function createServer() {
|
|
|
93
108
|
title: 'Get the full layout catalog',
|
|
94
109
|
description:
|
|
95
110
|
'Get every component that can be used as a standalone layout node, with its props ' +
|
|
96
|
-
|
|
111
|
+
"and slots. This is the same catalog Stark's own layout generator (Vecna) is restricted to.",
|
|
97
112
|
inputSchema: {
|
|
98
113
|
platform: z.enum(['web', 'native']).default('web').describe('Target platform'),
|
|
99
114
|
},
|
|
@@ -104,7 +119,7 @@ export function createServer() {
|
|
|
104
119
|
server.registerTool(
|
|
105
120
|
'get_layout_schema',
|
|
106
121
|
{
|
|
107
|
-
title:
|
|
122
|
+
title: "Get one component's layout schema",
|
|
108
123
|
description: 'Get the props/slots schema for a single layout-capable component.',
|
|
109
124
|
inputSchema: {
|
|
110
125
|
component: z.string().describe('Component name, e.g. "Card"'),
|
|
@@ -119,18 +134,28 @@ export function createServer() {
|
|
|
119
134
|
{
|
|
120
135
|
title: 'Validate a generated layout',
|
|
121
136
|
description:
|
|
122
|
-
|
|
137
|
+
"Run Stark's deterministic conformance checks (no LLM) against a LayoutConfig JSON: " +
|
|
123
138
|
'catalog membership, component do/dont rules, guardrails, and prop shape. Returns findings ' +
|
|
124
|
-
|
|
139
|
+
"ranked Critical/Warning/Info. Pass `canvasCases` (your renderer's supported node-type list) " +
|
|
125
140
|
'if you have one — without it, the catalog<->renderer parity check is skipped.',
|
|
126
141
|
inputSchema: {
|
|
127
|
-
layout: z
|
|
128
|
-
|
|
142
|
+
layout: z
|
|
143
|
+
.record(z.string(), z.any())
|
|
144
|
+
.describe('The LayoutConfig JSON, shaped { page: { sections: [...] } }'),
|
|
145
|
+
intent: z
|
|
146
|
+
.record(z.string(), z.any())
|
|
147
|
+
.optional()
|
|
148
|
+
.describe('Intent/guardrails object the layout was generated from'),
|
|
129
149
|
platform: z.enum(['web', 'native']).default('web'),
|
|
130
|
-
canvasCases: z
|
|
150
|
+
canvasCases: z
|
|
151
|
+
.array(z.string())
|
|
152
|
+
.optional()
|
|
153
|
+
.describe('Node types your renderer actually supports'),
|
|
131
154
|
},
|
|
132
155
|
},
|
|
133
|
-
wrap(({ layout, intent, platform, canvasCases }) =>
|
|
156
|
+
wrap(({ layout, intent, platform, canvasCases }) =>
|
|
157
|
+
validateLayout({ layout, intent, platform, canvasCases })
|
|
158
|
+
)
|
|
134
159
|
);
|
|
135
160
|
|
|
136
161
|
server.registerTool(
|
|
@@ -145,5 +170,35 @@ export function createServer() {
|
|
|
145
170
|
wrap(() => getGenerationProtocol())
|
|
146
171
|
);
|
|
147
172
|
|
|
173
|
+
server.registerTool(
|
|
174
|
+
'whisperer_context',
|
|
175
|
+
{
|
|
176
|
+
title: 'Ground an answer about the design system',
|
|
177
|
+
description:
|
|
178
|
+
'Everything a model needs to answer one question about Stark: the rules to answer under and ' +
|
|
179
|
+
'the facts to answer from — every component with its props per platform, every token with ' +
|
|
180
|
+
'its value, dark value, alias chain and React Native name — with the components and tokens ' +
|
|
181
|
+
'the question names expanded in full. The same brain behind Whisperer, the concierge in ' +
|
|
182
|
+
"Stark Dominion. Put the result in the system prompt and answer the question from it alone. " +
|
|
183
|
+
'Returns prose, not JSON.',
|
|
184
|
+
inputSchema: {
|
|
185
|
+
question: z.string().describe('What is being asked about the design system, verbatim'),
|
|
186
|
+
context: z
|
|
187
|
+
.array(z.string())
|
|
188
|
+
.optional()
|
|
189
|
+
.describe('The prior turns of the conversation, newest first, so a follow-up keeps its subject'),
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
// Not `wrap`: the answer is a system prompt, and a system prompt is text —
|
|
193
|
+
// a JSON-encoded string would hand the client every newline as `\n`.
|
|
194
|
+
async ({ question, context }) => {
|
|
195
|
+
try {
|
|
196
|
+
return { content: [{ type: 'text', text: whispererContext(question, context ?? []).text }] };
|
|
197
|
+
} catch (err) {
|
|
198
|
+
return error(err instanceof Error ? err.message : String(err));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
|
|
148
203
|
return server;
|
|
149
204
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for whisperer.js — the shared Whisperer brain. TypeScript consumers
|
|
3
|
+
* (Dominion) resolve this file ahead of the .js it sits beside.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface UsageDoc {
|
|
7
|
+
dos?: string[];
|
|
8
|
+
donts?: string[];
|
|
9
|
+
codeExample?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface NativeUsageProp {
|
|
13
|
+
name: string;
|
|
14
|
+
type: string;
|
|
15
|
+
default?: string;
|
|
16
|
+
required?: boolean | string;
|
|
17
|
+
description?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface NativeUsageDoc extends UsageDoc {
|
|
21
|
+
props?: NativeUsageProp[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PropMapEntry {
|
|
25
|
+
react: string;
|
|
26
|
+
type?: string;
|
|
27
|
+
values?: Record<string, string>;
|
|
28
|
+
transform?: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
componentProp?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ManifestComponent {
|
|
34
|
+
name: string;
|
|
35
|
+
slug: string;
|
|
36
|
+
status: string;
|
|
37
|
+
description: string;
|
|
38
|
+
platforms?: string[];
|
|
39
|
+
usage?: UsageDoc | null;
|
|
40
|
+
nativeUsage?: NativeUsageDoc | null;
|
|
41
|
+
props?: { web?: { propMap?: PropMapEntry[] } | null } | null;
|
|
42
|
+
tokens?: Record<string, unknown> | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface PropEntry {
|
|
46
|
+
name: string;
|
|
47
|
+
hasDefault?: boolean;
|
|
48
|
+
defaultSource?: string;
|
|
49
|
+
rest?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface SignaturePlatform {
|
|
53
|
+
package?: string;
|
|
54
|
+
components?: Record<string, { props?: PropEntry[] }>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface TokenMeta {
|
|
58
|
+
value: unknown;
|
|
59
|
+
type: string;
|
|
60
|
+
layer: 'primitive' | 'semantic' | 'component' | null;
|
|
61
|
+
alias: string | null;
|
|
62
|
+
description: string | null;
|
|
63
|
+
rn: { name: string; module: string } | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface WhispererData {
|
|
67
|
+
manifest: { components: ManifestComponent[] };
|
|
68
|
+
catalog: { platforms: Record<string, { exports: Record<string, { kind: string }> }> };
|
|
69
|
+
componentProps: { platforms: { web?: SignaturePlatform; native?: SignaturePlatform } };
|
|
70
|
+
tokenMeta: Record<string, TokenMeta>;
|
|
71
|
+
tokenDark: Record<string, unknown>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface Grounding {
|
|
75
|
+
/** The grounding block, ready to sit under the system prompt. */
|
|
76
|
+
text: string;
|
|
77
|
+
/** The components whose full detail the block carries. */
|
|
78
|
+
components: string[];
|
|
79
|
+
/** The subset the question itself named. */
|
|
80
|
+
named: string[];
|
|
81
|
+
/** The tokens the question named outright. */
|
|
82
|
+
tokens: string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface Whisperer {
|
|
86
|
+
/** Where each platform's components are imported from. */
|
|
87
|
+
PACKAGES: { web: string | null; native: string | null };
|
|
88
|
+
/** The grounding block for one question; `context` is the prior turns, newest first. */
|
|
89
|
+
buildGrounding(question: string, context?: string[]): Grounding;
|
|
90
|
+
/** Every component the text names, in reading order, uncapped. */
|
|
91
|
+
componentsNamedIn(text: string): { name: string; platforms: string[] }[];
|
|
92
|
+
isKnownComponent(name: string): boolean;
|
|
93
|
+
/** Whether a token name (with or without its leading `--`) exists. */
|
|
94
|
+
isKnownToken(name: string): boolean;
|
|
95
|
+
/** Every token the text names, as CSS name or React Native export. */
|
|
96
|
+
tokensInQuestion(text: string): string[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The grounding functions, built over one set of generated files. */
|
|
100
|
+
export function createWhisperer(data: WhispererData): Whisperer;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The design-system rules the grounding is handed to a model under. A
|
|
104
|
+
* surface appends its own rules — how it renders, what it can see.
|
|
105
|
+
*/
|
|
106
|
+
export const WHISPERER_SYSTEM_PROMPT: string;
|