capy-mcp 1.0.0 → 1.0.2
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/LICENSE +21 -0
- package/README.md +92 -0
- package/dist/brief.d.ts +6 -0
- package/dist/brief.js +129 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +6 -0
- package/dist/design-system.d.ts +3 -0
- package/dist/design-system.js +176 -0
- package/dist/files.d.ts +4 -0
- package/dist/files.js +26 -0
- package/dist/framework.d.ts +2 -0
- package/dist/framework.js +115 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/project.d.ts +2 -0
- package/dist/project.js +61 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +204 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.js +1 -0
- package/package.json +54 -4
- package/bin/cli.js +0 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anant Singhal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# capy-mcp
|
|
2
|
+
|
|
3
|
+
`capy-mcp` is a stdio MCP server that inspects a React or Next.js repo and returns a structured brief for building or updating a local `/preview` route.
|
|
4
|
+
|
|
5
|
+
It does not generate the preview page itself. It gives an AI coding agent the repo map, inspection order, constraints, warnings, and target preview file so the agent can implement `/preview` in the host app.
|
|
6
|
+
|
|
7
|
+
## What it exposes
|
|
8
|
+
|
|
9
|
+
The server currently registers two MCP tools:
|
|
10
|
+
|
|
11
|
+
- `get_preview_brief`
|
|
12
|
+
- `get_design_system`
|
|
13
|
+
|
|
14
|
+
The tool accepts:
|
|
15
|
+
|
|
16
|
+
- `task`: `"build_preview"` or `"update_preview"`
|
|
17
|
+
- `changedFiles?`: a list of changed files to bias incremental updates
|
|
18
|
+
- `userGoal?`: the end-user request so the brief stays aligned with the task
|
|
19
|
+
|
|
20
|
+
The tool returns structured content with:
|
|
21
|
+
|
|
22
|
+
- `project_facts`
|
|
23
|
+
- `inspection_plan`
|
|
24
|
+
- `constraints`
|
|
25
|
+
- `deliverable_spec`
|
|
26
|
+
- `update_strategy`
|
|
27
|
+
- `warnings`
|
|
28
|
+
- `instructions`
|
|
29
|
+
|
|
30
|
+
`get_design_system` writes a stable JSON artifact, by default at `.capy/design-system.json`, and returns a summary plus the artifact contents. This file is meant to be the machine-readable source of truth that later coding sessions can load before making UI changes.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install capy-mcp
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run it directly:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx capy-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## MCP config
|
|
45
|
+
|
|
46
|
+
Example stdio MCP config:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"capy": {
|
|
52
|
+
"command": "npx",
|
|
53
|
+
"args": ["-y", "capy-mcp"]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Library usage
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import {
|
|
63
|
+
buildDesignSystemArtifact,
|
|
64
|
+
buildPreviewBrief,
|
|
65
|
+
detectFramework,
|
|
66
|
+
writeDesignSystemArtifact,
|
|
67
|
+
} from "capy-mcp";
|
|
68
|
+
|
|
69
|
+
const framework = await detectFramework(process.cwd());
|
|
70
|
+
const brief = await buildPreviewBrief(process.cwd(), {
|
|
71
|
+
task: "build_preview",
|
|
72
|
+
});
|
|
73
|
+
const designSystem = await buildDesignSystemArtifact(process.cwd());
|
|
74
|
+
await writeDesignSystemArtifact(process.cwd());
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Local development
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install
|
|
81
|
+
npm test
|
|
82
|
+
npx capy-mcp
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Publish checklist
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm test
|
|
89
|
+
npm publish
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
If `capy-mcp` is not owned by your npm account, rename the package before publishing.
|
package/dist/brief.d.ts
ADDED
package/dist/brief.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { detectFramework } from "./framework.js";
|
|
2
|
+
import { buildProjectFacts } from "./project.js";
|
|
3
|
+
const SECTION_ORDER = [
|
|
4
|
+
"Foundations",
|
|
5
|
+
"Colors",
|
|
6
|
+
"Typography",
|
|
7
|
+
"Spacing",
|
|
8
|
+
"Inputs",
|
|
9
|
+
"Actions",
|
|
10
|
+
"Navigation",
|
|
11
|
+
"Data Display",
|
|
12
|
+
"Feedback",
|
|
13
|
+
"Overlays",
|
|
14
|
+
"Feature or Page Sections",
|
|
15
|
+
];
|
|
16
|
+
export async function buildPreviewBrief(projectRoot, input) {
|
|
17
|
+
const framework = await detectFramework(projectRoot);
|
|
18
|
+
const projectFacts = await buildProjectFacts(projectRoot, framework);
|
|
19
|
+
const warnings = buildWarnings(framework, input.changedFiles);
|
|
20
|
+
return {
|
|
21
|
+
projectFacts,
|
|
22
|
+
inspectionPlan: buildInspectionPlan(projectFacts),
|
|
23
|
+
constraints: buildConstraints(framework),
|
|
24
|
+
deliverableSpec: {
|
|
25
|
+
goal: input.task === "update_preview"
|
|
26
|
+
? "Update the existing /preview surface incrementally based on the files or areas that changed."
|
|
27
|
+
: "Create a local /preview route that helps humans and agents inspect the app's real UI system quickly.",
|
|
28
|
+
layout: "vertical-scroll",
|
|
29
|
+
allowHorizontalRows: true,
|
|
30
|
+
previewRoute: projectFacts.previewRoute,
|
|
31
|
+
previewEntryFile: projectFacts.previewEntryFile,
|
|
32
|
+
sections: SECTION_ORDER,
|
|
33
|
+
useExistingComponentsFirst: true,
|
|
34
|
+
},
|
|
35
|
+
updateStrategy: buildUpdateStrategy(input.changedFiles),
|
|
36
|
+
warnings,
|
|
37
|
+
instructions: buildInstructions(projectFacts, input),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function buildInspectionPlan(projectFacts) {
|
|
41
|
+
const appShellTargets = uniqueCompact([
|
|
42
|
+
firstMatching(projectFacts.likelyPageDirs, "src/app")
|
|
43
|
+
? "src/app/layout.tsx"
|
|
44
|
+
: undefined,
|
|
45
|
+
firstMatching(projectFacts.likelyPageDirs, "src/app")
|
|
46
|
+
? "src/app/page.tsx"
|
|
47
|
+
: undefined,
|
|
48
|
+
firstMatching(projectFacts.likelyPageDirs, "app") ? "app/layout.tsx" : undefined,
|
|
49
|
+
firstMatching(projectFacts.likelyPageDirs, "app") ? "app/page.tsx" : undefined,
|
|
50
|
+
firstMatching(projectFacts.likelyPageDirs, "src/pages") ? "src/pages/_app.tsx" : undefined,
|
|
51
|
+
firstMatching(projectFacts.likelyPageDirs, "pages") ? "pages/_app.tsx" : undefined,
|
|
52
|
+
]);
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
step: 1,
|
|
56
|
+
action: "Read the app shell and routing entry points first",
|
|
57
|
+
targets: appShellTargets.length > 0 ? appShellTargets : projectFacts.likelyPageDirs,
|
|
58
|
+
reason: "Understand the project structure, routing style, and baseline visual language.",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
step: 2,
|
|
62
|
+
action: "Read global styles and theme sources",
|
|
63
|
+
targets: projectFacts.likelyStyleFiles.slice(0, 8),
|
|
64
|
+
reason: "Extract colors, spacing, typography, layout rules, and theme conventions from the real repo.",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
step: 3,
|
|
68
|
+
action: "Inspect UI/component directories",
|
|
69
|
+
targets: projectFacts.likelyComponentDirs.length > 0 ? projectFacts.likelyComponentDirs : projectFacts.likelyUiDirs,
|
|
70
|
+
reason: "Find existing primitives, composites, and sections before inventing new preview-only UI.",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
step: 4,
|
|
74
|
+
action: "Implement or update the preview route",
|
|
75
|
+
targets: [projectFacts.previewEntryFile],
|
|
76
|
+
reason: "Build a neat, scrollable /preview page that reflects the real app structure.",
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
function buildConstraints(framework) {
|
|
81
|
+
const constraints = [
|
|
82
|
+
"Do not invent colors, spacing, typography, or component APIs before inspecting the repo files listed in inspection_plan.",
|
|
83
|
+
"Build a vertically scrollable preview page.",
|
|
84
|
+
"Use horizontal specimen rows only when they make scanning easier.",
|
|
85
|
+
"Prefer existing components over creating preview-only components.",
|
|
86
|
+
"Keep the page neat, easy to scan, and aligned with the app's current design language.",
|
|
87
|
+
];
|
|
88
|
+
if (framework.needsConfirmation && framework.confirmationMessage) {
|
|
89
|
+
constraints.push(framework.confirmationMessage);
|
|
90
|
+
}
|
|
91
|
+
return constraints;
|
|
92
|
+
}
|
|
93
|
+
function buildUpdateStrategy(changedFiles) {
|
|
94
|
+
const strategies = [
|
|
95
|
+
"When updating, inspect changed files first and revise only affected preview sections where possible.",
|
|
96
|
+
"If a changed file affects shared foundations, then update every preview section that depends on those foundations.",
|
|
97
|
+
"Do not rebuild the entire preview page unless the current structure is no longer representative of the app.",
|
|
98
|
+
];
|
|
99
|
+
if (changedFiles && changedFiles.length > 0) {
|
|
100
|
+
strategies.unshift(`Prioritize these changed files: ${changedFiles.join(", ")}`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
strategies.unshift("If changed files are not provided, inspect git diff or the user's latest edits before deciding what to update.");
|
|
104
|
+
}
|
|
105
|
+
return strategies;
|
|
106
|
+
}
|
|
107
|
+
function buildWarnings(framework, changedFiles) {
|
|
108
|
+
const warnings = [];
|
|
109
|
+
if (framework.confirmationMessage) {
|
|
110
|
+
warnings.push(framework.confirmationMessage);
|
|
111
|
+
}
|
|
112
|
+
if (!changedFiles || changedFiles.length === 0) {
|
|
113
|
+
warnings.push("No changedFiles were provided. The agent should inspect git diff or recent edits when performing update_preview.");
|
|
114
|
+
}
|
|
115
|
+
return warnings;
|
|
116
|
+
}
|
|
117
|
+
function buildInstructions(projectFacts, input) {
|
|
118
|
+
const lead = input.task === "update_preview"
|
|
119
|
+
? "Update the existing /preview route incrementally."
|
|
120
|
+
: "Create the /preview route from scratch.";
|
|
121
|
+
const userGoal = input.userGoal ? ` User goal: ${input.userGoal}.` : "";
|
|
122
|
+
return `${lead}${userGoal} Read the app shell first, then global styles, then component directories. After that, implement ${projectFacts.previewEntryFile} as a vertically scrollable preview surface using the repo's real design language and existing components.`;
|
|
123
|
+
}
|
|
124
|
+
function uniqueCompact(values) {
|
|
125
|
+
return Array.from(new Set(values.filter(Boolean)));
|
|
126
|
+
}
|
|
127
|
+
function firstMatching(values, needle) {
|
|
128
|
+
return values.find((value) => value === needle);
|
|
129
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { DesignSystemArtifact, DesignSystemBuildInput } from "./types.js";
|
|
2
|
+
export declare function buildDesignSystemArtifact(projectRoot: string, input?: DesignSystemBuildInput): Promise<DesignSystemArtifact>;
|
|
3
|
+
export declare function writeDesignSystemArtifact(projectRoot: string, input?: DesignSystemBuildInput): Promise<DesignSystemArtifact>;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import { basename, join } from "path";
|
|
3
|
+
import { buildPreviewBrief } from "./brief.js";
|
|
4
|
+
import { readText, toPosixPath, writeText } from "./files.js";
|
|
5
|
+
import { detectFramework } from "./framework.js";
|
|
6
|
+
import { buildProjectFacts } from "./project.js";
|
|
7
|
+
const DEFAULT_ARTIFACT_PATH = ".capy/design-system.json";
|
|
8
|
+
export async function buildDesignSystemArtifact(projectRoot, input = {}) {
|
|
9
|
+
const framework = await detectFramework(projectRoot);
|
|
10
|
+
const projectFacts = await buildProjectFacts(projectRoot, framework);
|
|
11
|
+
const previewBrief = await buildPreviewBrief(projectRoot, {
|
|
12
|
+
task: input.mode === "update" ? "update_preview" : "build_preview",
|
|
13
|
+
changedFiles: input.changedFiles,
|
|
14
|
+
userGoal: input.userGoal,
|
|
15
|
+
});
|
|
16
|
+
const cssVariables = await collectCssVariables(projectRoot, projectFacts.likelyStyleFiles);
|
|
17
|
+
const components = await collectComponents(projectRoot, projectFacts);
|
|
18
|
+
const artifactPath = input.artifactPath ?? DEFAULT_ARTIFACT_PATH;
|
|
19
|
+
const sourceFiles = Array.from(new Set([
|
|
20
|
+
...projectFacts.likelyStyleFiles,
|
|
21
|
+
...components.map((component) => component.path),
|
|
22
|
+
])).sort();
|
|
23
|
+
return {
|
|
24
|
+
meta: {
|
|
25
|
+
generatedAt: new Date().toISOString(),
|
|
26
|
+
mode: input.mode ?? "build",
|
|
27
|
+
artifactPath,
|
|
28
|
+
framework: framework.kind,
|
|
29
|
+
routingStyle: framework.routingStyle,
|
|
30
|
+
previewRoute: framework.previewRoute,
|
|
31
|
+
previewEntryFile: framework.previewEntryFile,
|
|
32
|
+
packageManager: framework.packageManager,
|
|
33
|
+
changedFiles: input.changedFiles ?? [],
|
|
34
|
+
sourceFiles,
|
|
35
|
+
},
|
|
36
|
+
directories: {
|
|
37
|
+
componentDirs: projectFacts.likelyComponentDirs,
|
|
38
|
+
pageDirs: projectFacts.likelyPageDirs,
|
|
39
|
+
styleFiles: projectFacts.likelyStyleFiles,
|
|
40
|
+
uiDirs: projectFacts.likelyUiDirs,
|
|
41
|
+
},
|
|
42
|
+
tokens: {
|
|
43
|
+
cssVariables,
|
|
44
|
+
styleFiles: projectFacts.likelyStyleFiles,
|
|
45
|
+
},
|
|
46
|
+
components,
|
|
47
|
+
preview: {
|
|
48
|
+
route: projectFacts.previewRoute,
|
|
49
|
+
entryFile: projectFacts.previewEntryFile,
|
|
50
|
+
sections: previewBrief.deliverableSpec.sections,
|
|
51
|
+
updateStrategy: previewBrief.updateStrategy,
|
|
52
|
+
},
|
|
53
|
+
guidance: {
|
|
54
|
+
summary: buildSummary(projectFacts, framework, components, cssVariables),
|
|
55
|
+
warnings: previewBrief.warnings,
|
|
56
|
+
instructions: [
|
|
57
|
+
"Read this file before making UI changes or updating /preview.",
|
|
58
|
+
"Prefer the listed components, tokens, and preview sections over inventing new UI structure.",
|
|
59
|
+
input.mode === "update"
|
|
60
|
+
? "When updating, inspect changedFiles first and only refresh affected sections unless foundations changed."
|
|
61
|
+
: "If this artifact looks incomplete, inspect the listed sourceFiles before expanding it.",
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export async function writeDesignSystemArtifact(projectRoot, input = {}) {
|
|
67
|
+
const artifact = await buildDesignSystemArtifact(projectRoot, input);
|
|
68
|
+
const artifactFile = join(projectRoot, artifact.meta.artifactPath);
|
|
69
|
+
await writeText(artifactFile, `${JSON.stringify(artifact, null, 2)}\n`);
|
|
70
|
+
return artifact;
|
|
71
|
+
}
|
|
72
|
+
async function collectCssVariables(projectRoot, styleFiles) {
|
|
73
|
+
const variables = [];
|
|
74
|
+
for (const relativePath of styleFiles) {
|
|
75
|
+
const fileContents = await readText(join(projectRoot, relativePath));
|
|
76
|
+
if (!fileContents)
|
|
77
|
+
continue;
|
|
78
|
+
const regex = /--([A-Za-z0-9-_]+)\s*:\s*([^;}{]+);/g;
|
|
79
|
+
let match;
|
|
80
|
+
while ((match = regex.exec(fileContents)) !== null) {
|
|
81
|
+
variables.push({
|
|
82
|
+
name: `--${match[1]}`,
|
|
83
|
+
value: match[2].trim(),
|
|
84
|
+
category: classifyCssVariable(match[1]),
|
|
85
|
+
file: relativePath,
|
|
86
|
+
line: lineNumberAt(fileContents, match.index),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return variables;
|
|
91
|
+
}
|
|
92
|
+
async function collectComponents(projectRoot, projectFacts) {
|
|
93
|
+
if (projectFacts.likelyComponentDirs.length === 0) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
const patterns = projectFacts.likelyComponentDirs.map((dir) => `${dir}/**/*.{tsx,jsx,ts,js}`);
|
|
97
|
+
const files = await glob(patterns, {
|
|
98
|
+
cwd: projectRoot,
|
|
99
|
+
nodir: true,
|
|
100
|
+
ignore: [
|
|
101
|
+
"**/*.test.*",
|
|
102
|
+
"**/*.spec.*",
|
|
103
|
+
"**/*.stories.*",
|
|
104
|
+
"**/*.story.*",
|
|
105
|
+
"**/node_modules/**",
|
|
106
|
+
"**/.next/**",
|
|
107
|
+
"**/dist/**",
|
|
108
|
+
"**/build/**",
|
|
109
|
+
"**/coverage/**",
|
|
110
|
+
"**/preview/**",
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
const componentRecords = await Promise.all(files
|
|
114
|
+
.map((file) => toPosixPath(file))
|
|
115
|
+
.sort()
|
|
116
|
+
.map(async (relativePath) => {
|
|
117
|
+
const contents = await readText(join(projectRoot, relativePath));
|
|
118
|
+
const exports = contents ? extractExports(contents) : [];
|
|
119
|
+
const fallbackName = basename(relativePath).replace(/\.[^.]+$/, "");
|
|
120
|
+
return {
|
|
121
|
+
name: exports[0] ?? fallbackName,
|
|
122
|
+
path: relativePath,
|
|
123
|
+
exports,
|
|
124
|
+
kind: classifyComponentKind(relativePath),
|
|
125
|
+
};
|
|
126
|
+
}));
|
|
127
|
+
return componentRecords;
|
|
128
|
+
}
|
|
129
|
+
function buildSummary(projectFacts, framework, components, cssVariables) {
|
|
130
|
+
return [
|
|
131
|
+
`Framework: ${framework.label}.`,
|
|
132
|
+
`Preview target: ${projectFacts.previewEntryFile}.`,
|
|
133
|
+
`Found ${components.length} component candidates and ${cssVariables.length} CSS variables.`,
|
|
134
|
+
"Use this artifact as the canonical machine-readable UI map before editing the app or /preview.",
|
|
135
|
+
].join(" ");
|
|
136
|
+
}
|
|
137
|
+
function extractExports(contents) {
|
|
138
|
+
const exports = new Set();
|
|
139
|
+
const patterns = [
|
|
140
|
+
/export\s+function\s+([A-Z][A-Za-z0-9_]*)/g,
|
|
141
|
+
/export\s+const\s+([A-Z][A-Za-z0-9_]*)/g,
|
|
142
|
+
/export\s+class\s+([A-Z][A-Za-z0-9_]*)/g,
|
|
143
|
+
/export\s+default\s+function\s+([A-Z][A-Za-z0-9_]*)/g,
|
|
144
|
+
];
|
|
145
|
+
for (const pattern of patterns) {
|
|
146
|
+
let match;
|
|
147
|
+
while ((match = pattern.exec(contents)) !== null) {
|
|
148
|
+
exports.add(match[1]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return Array.from(exports);
|
|
152
|
+
}
|
|
153
|
+
function classifyComponentKind(relativePath) {
|
|
154
|
+
if (relativePath.includes("/features/"))
|
|
155
|
+
return "feature";
|
|
156
|
+
if (relativePath.includes("/ui/"))
|
|
157
|
+
return "primitive";
|
|
158
|
+
if (relativePath.includes("/components/"))
|
|
159
|
+
return "component";
|
|
160
|
+
return "unknown";
|
|
161
|
+
}
|
|
162
|
+
function classifyCssVariable(name) {
|
|
163
|
+
if (/(color|bg|text|border|surface|accent|primary|secondary|foreground)/i.test(name)) {
|
|
164
|
+
return "color";
|
|
165
|
+
}
|
|
166
|
+
if (/(font|type|text|leading|tracking|heading|body)/i.test(name)) {
|
|
167
|
+
return "typography";
|
|
168
|
+
}
|
|
169
|
+
if (/(space|spacing|gap|radius|shadow|size|width|height)/i.test(name)) {
|
|
170
|
+
return "layout";
|
|
171
|
+
}
|
|
172
|
+
return "other";
|
|
173
|
+
}
|
|
174
|
+
function lineNumberAt(contents, index) {
|
|
175
|
+
return contents.slice(0, index).split("\n").length;
|
|
176
|
+
}
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function fileExists(path: string): Promise<boolean>;
|
|
2
|
+
export declare function readText(path: string): Promise<string | null>;
|
|
3
|
+
export declare function writeText(path: string, contents: string): Promise<void>;
|
|
4
|
+
export declare function toPosixPath(path: string): string;
|
package/dist/files.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
export async function fileExists(path) {
|
|
4
|
+
try {
|
|
5
|
+
await stat(path);
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function readText(path) {
|
|
13
|
+
try {
|
|
14
|
+
return await readFile(path, "utf8");
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function writeText(path, contents) {
|
|
21
|
+
await mkdir(dirname(path), { recursive: true });
|
|
22
|
+
await writeFile(path, contents, "utf8");
|
|
23
|
+
}
|
|
24
|
+
export function toPosixPath(path) {
|
|
25
|
+
return path.replace(/\\/g, "/");
|
|
26
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { fileExists, readText } from "./files.js";
|
|
3
|
+
export async function detectFramework(projectRoot) {
|
|
4
|
+
const packageJson = await loadPackageJson(projectRoot);
|
|
5
|
+
const deps = {
|
|
6
|
+
...packageJson?.dependencies,
|
|
7
|
+
...packageJson?.devDependencies,
|
|
8
|
+
};
|
|
9
|
+
const hasNext = Boolean(deps.next);
|
|
10
|
+
const hasReact = Boolean(deps.react);
|
|
11
|
+
const hasReactRouter = Boolean(deps["react-router-dom"]);
|
|
12
|
+
const srcAppDir = join(projectRoot, "src/app");
|
|
13
|
+
const appDir = join(projectRoot, "app");
|
|
14
|
+
const srcPagesDir = join(projectRoot, "src/pages");
|
|
15
|
+
const pagesDir = join(projectRoot, "pages");
|
|
16
|
+
if (hasNext && (await fileExists(srcAppDir))) {
|
|
17
|
+
return {
|
|
18
|
+
kind: "next-app-router",
|
|
19
|
+
label: "Next.js App Router",
|
|
20
|
+
routingStyle: "app-router",
|
|
21
|
+
previewRoute: "/preview",
|
|
22
|
+
previewEntryFile: "src/app/preview/page.tsx",
|
|
23
|
+
packageManager: detectPackageManager(packageJson?.packageManager),
|
|
24
|
+
needsConfirmation: false,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (hasNext && (await fileExists(appDir))) {
|
|
28
|
+
return {
|
|
29
|
+
kind: "next-app-router",
|
|
30
|
+
label: "Next.js App Router",
|
|
31
|
+
routingStyle: "app-router",
|
|
32
|
+
previewRoute: "/preview",
|
|
33
|
+
previewEntryFile: "app/preview/page.tsx",
|
|
34
|
+
packageManager: detectPackageManager(packageJson?.packageManager),
|
|
35
|
+
needsConfirmation: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (hasNext && (await fileExists(srcPagesDir))) {
|
|
39
|
+
return {
|
|
40
|
+
kind: "next-pages-router",
|
|
41
|
+
label: "Next.js Pages Router",
|
|
42
|
+
routingStyle: "pages-router",
|
|
43
|
+
previewRoute: "/preview",
|
|
44
|
+
previewEntryFile: "src/pages/preview.tsx",
|
|
45
|
+
packageManager: detectPackageManager(packageJson?.packageManager),
|
|
46
|
+
needsConfirmation: false,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (hasNext && (await fileExists(pagesDir))) {
|
|
50
|
+
return {
|
|
51
|
+
kind: "next-pages-router",
|
|
52
|
+
label: "Next.js Pages Router",
|
|
53
|
+
routingStyle: "pages-router",
|
|
54
|
+
previewRoute: "/preview",
|
|
55
|
+
previewEntryFile: "pages/preview.tsx",
|
|
56
|
+
packageManager: detectPackageManager(packageJson?.packageManager),
|
|
57
|
+
needsConfirmation: false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (hasReact && hasReactRouter) {
|
|
61
|
+
return {
|
|
62
|
+
kind: "react-router",
|
|
63
|
+
label: "React + React Router",
|
|
64
|
+
routingStyle: "react-router",
|
|
65
|
+
previewRoute: "/preview",
|
|
66
|
+
previewEntryFile: "src/routes/preview.tsx",
|
|
67
|
+
packageManager: detectPackageManager(packageJson?.packageManager),
|
|
68
|
+
needsConfirmation: false,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (hasReact) {
|
|
72
|
+
return {
|
|
73
|
+
kind: "react-no-router",
|
|
74
|
+
label: "React without router",
|
|
75
|
+
routingStyle: "none",
|
|
76
|
+
previewRoute: "/preview",
|
|
77
|
+
previewEntryFile: "src/routes/preview.tsx",
|
|
78
|
+
packageManager: detectPackageManager(packageJson?.packageManager),
|
|
79
|
+
needsConfirmation: true,
|
|
80
|
+
confirmationMessage: "This repo looks like React without react-router-dom. The agent should confirm router setup before implementing /preview.",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
kind: "unknown",
|
|
85
|
+
label: "Unknown framework",
|
|
86
|
+
routingStyle: "unknown",
|
|
87
|
+
previewRoute: "/preview",
|
|
88
|
+
previewEntryFile: "preview-entry-file-unknown",
|
|
89
|
+
packageManager: detectPackageManager(packageJson?.packageManager),
|
|
90
|
+
needsConfirmation: true,
|
|
91
|
+
confirmationMessage: "Framework detection was inconclusive. The agent should inspect routing manually before building /preview.",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function loadPackageJson(projectRoot) {
|
|
95
|
+
const raw = await readText(join(projectRoot, "package.json"));
|
|
96
|
+
if (!raw)
|
|
97
|
+
return null;
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(raw);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function detectPackageManager(packageManager) {
|
|
106
|
+
if (!packageManager)
|
|
107
|
+
return "npm";
|
|
108
|
+
if (packageManager.startsWith("pnpm"))
|
|
109
|
+
return "pnpm";
|
|
110
|
+
if (packageManager.startsWith("yarn"))
|
|
111
|
+
return "yarn";
|
|
112
|
+
if (packageManager.startsWith("bun"))
|
|
113
|
+
return "bun";
|
|
114
|
+
return "npm";
|
|
115
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { buildPreviewBrief } from "./brief.js";
|
|
2
|
+
export { buildDesignSystemArtifact, writeDesignSystemArtifact } from "./design-system.js";
|
|
3
|
+
export { detectFramework } from "./framework.js";
|
|
4
|
+
export { createServer, main } from "./server.js";
|
|
5
|
+
export type { ComponentRecord, CssVariableRecord, DesignSystemArtifact, DesignSystemBuildInput, DeliverableSpec, FrameworkInfo, FrameworkKind, InspectionStep, PreviewBrief, ProjectFacts, RoutingStyle, } from "./types.js";
|
package/dist/index.js
ADDED
package/dist/project.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { fileExists, toPosixPath } from "./files.js";
|
|
4
|
+
export async function buildProjectFacts(projectRoot, framework) {
|
|
5
|
+
const componentDirs = await findExistingDirectories(projectRoot, [
|
|
6
|
+
"src/components",
|
|
7
|
+
"src/ui",
|
|
8
|
+
"src/features",
|
|
9
|
+
"components",
|
|
10
|
+
"ui",
|
|
11
|
+
"features",
|
|
12
|
+
]);
|
|
13
|
+
const pageDirs = await findExistingDirectories(projectRoot, [
|
|
14
|
+
"src/app",
|
|
15
|
+
"app",
|
|
16
|
+
"src/pages",
|
|
17
|
+
"pages",
|
|
18
|
+
"src/routes",
|
|
19
|
+
"routes",
|
|
20
|
+
]);
|
|
21
|
+
const styleFiles = await findStyleFiles(projectRoot);
|
|
22
|
+
const likelyUiDirs = Array.from(new Set([...componentDirs, ...pageDirs.filter((dir) => !dir.endsWith("/preview"))]));
|
|
23
|
+
return {
|
|
24
|
+
framework: framework.kind,
|
|
25
|
+
routingStyle: framework.routingStyle,
|
|
26
|
+
previewRoute: framework.previewRoute,
|
|
27
|
+
previewEntryFile: framework.previewEntryFile,
|
|
28
|
+
packageManager: framework.packageManager,
|
|
29
|
+
likelyComponentDirs: componentDirs,
|
|
30
|
+
likelyStyleFiles: styleFiles,
|
|
31
|
+
likelyPageDirs: pageDirs,
|
|
32
|
+
likelyUiDirs,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function findExistingDirectories(projectRoot, candidates) {
|
|
36
|
+
const results = [];
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
if (await fileExists(join(projectRoot, candidate))) {
|
|
39
|
+
results.push(candidate);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return results;
|
|
43
|
+
}
|
|
44
|
+
async function findStyleFiles(projectRoot) {
|
|
45
|
+
const files = await glob(["**/*.{css,scss,sass,less}"], {
|
|
46
|
+
cwd: projectRoot,
|
|
47
|
+
nodir: true,
|
|
48
|
+
ignore: [
|
|
49
|
+
"**/node_modules/**",
|
|
50
|
+
"**/.next/**",
|
|
51
|
+
"**/dist/**",
|
|
52
|
+
"**/build/**",
|
|
53
|
+
"**/.capy/**",
|
|
54
|
+
"**/coverage/**",
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
return files
|
|
58
|
+
.map((file) => toPosixPath(file))
|
|
59
|
+
.filter((file) => !file.includes("/preview/"))
|
|
60
|
+
.sort();
|
|
61
|
+
}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { buildPreviewBrief } from "./brief.js";
|
|
6
|
+
import { writeDesignSystemArtifact } from "./design-system.js";
|
|
7
|
+
export function createServer(projectRoot = process.cwd()) {
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: "capy",
|
|
10
|
+
version: "1.0.2",
|
|
11
|
+
});
|
|
12
|
+
server.registerTool("get_preview_brief", {
|
|
13
|
+
title: "Get Preview Brief",
|
|
14
|
+
description: "Call this first for any UI preview, design-system, component-catalog, or style-audit task. Returns the repo map, inspection plan, constraints, and exact /preview spec the agent should follow before writing code.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
task: z
|
|
17
|
+
.enum(["build_preview", "update_preview"])
|
|
18
|
+
.default("build_preview")
|
|
19
|
+
.describe("Whether the agent is creating /preview from scratch or updating it."),
|
|
20
|
+
changedFiles: z
|
|
21
|
+
.array(z.string())
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Files changed since the previous preview pass, when known."),
|
|
24
|
+
userGoal: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("The user's request, so the returned instructions can stay aligned."),
|
|
28
|
+
},
|
|
29
|
+
outputSchema: {
|
|
30
|
+
project_facts: z.object({
|
|
31
|
+
framework: z.enum([
|
|
32
|
+
"next-app-router",
|
|
33
|
+
"next-pages-router",
|
|
34
|
+
"react-router",
|
|
35
|
+
"react-no-router",
|
|
36
|
+
"unknown",
|
|
37
|
+
]),
|
|
38
|
+
routing_style: z.enum([
|
|
39
|
+
"app-router",
|
|
40
|
+
"pages-router",
|
|
41
|
+
"react-router",
|
|
42
|
+
"none",
|
|
43
|
+
"unknown",
|
|
44
|
+
]),
|
|
45
|
+
preview_route: z.string(),
|
|
46
|
+
preview_entry_file: z.string(),
|
|
47
|
+
package_manager: z.enum(["npm", "pnpm", "yarn", "bun"]),
|
|
48
|
+
likely_component_dirs: z.array(z.string()),
|
|
49
|
+
likely_style_files: z.array(z.string()),
|
|
50
|
+
likely_page_dirs: z.array(z.string()),
|
|
51
|
+
likely_ui_dirs: z.array(z.string()),
|
|
52
|
+
}),
|
|
53
|
+
inspection_plan: z.array(z.object({
|
|
54
|
+
step: z.number(),
|
|
55
|
+
action: z.string(),
|
|
56
|
+
targets: z.array(z.string()),
|
|
57
|
+
reason: z.string(),
|
|
58
|
+
})),
|
|
59
|
+
constraints: z.array(z.string()),
|
|
60
|
+
deliverable_spec: z.object({
|
|
61
|
+
goal: z.string(),
|
|
62
|
+
layout: z.literal("vertical-scroll"),
|
|
63
|
+
allow_horizontal_rows: z.boolean(),
|
|
64
|
+
preview_route: z.string(),
|
|
65
|
+
preview_entry_file: z.string(),
|
|
66
|
+
sections: z.array(z.string()),
|
|
67
|
+
use_existing_components_first: z.boolean(),
|
|
68
|
+
}),
|
|
69
|
+
update_strategy: z.array(z.string()),
|
|
70
|
+
warnings: z.array(z.string()),
|
|
71
|
+
instructions: z.string(),
|
|
72
|
+
},
|
|
73
|
+
}, async ({ task = "build_preview", changedFiles, userGoal }) => {
|
|
74
|
+
const brief = await buildPreviewBrief(projectRoot, {
|
|
75
|
+
task,
|
|
76
|
+
changedFiles,
|
|
77
|
+
userGoal,
|
|
78
|
+
});
|
|
79
|
+
const structuredContent = {
|
|
80
|
+
project_facts: {
|
|
81
|
+
framework: brief.projectFacts.framework,
|
|
82
|
+
routing_style: brief.projectFacts.routingStyle,
|
|
83
|
+
preview_route: brief.projectFacts.previewRoute,
|
|
84
|
+
preview_entry_file: brief.projectFacts.previewEntryFile,
|
|
85
|
+
package_manager: brief.projectFacts.packageManager,
|
|
86
|
+
likely_component_dirs: brief.projectFacts.likelyComponentDirs,
|
|
87
|
+
likely_style_files: brief.projectFacts.likelyStyleFiles,
|
|
88
|
+
likely_page_dirs: brief.projectFacts.likelyPageDirs,
|
|
89
|
+
likely_ui_dirs: brief.projectFacts.likelyUiDirs,
|
|
90
|
+
},
|
|
91
|
+
inspection_plan: brief.inspectionPlan.map((item) => ({
|
|
92
|
+
step: item.step,
|
|
93
|
+
action: item.action,
|
|
94
|
+
targets: item.targets,
|
|
95
|
+
reason: item.reason,
|
|
96
|
+
})),
|
|
97
|
+
constraints: brief.constraints,
|
|
98
|
+
deliverable_spec: {
|
|
99
|
+
goal: brief.deliverableSpec.goal,
|
|
100
|
+
layout: brief.deliverableSpec.layout,
|
|
101
|
+
allow_horizontal_rows: brief.deliverableSpec.allowHorizontalRows,
|
|
102
|
+
preview_route: brief.deliverableSpec.previewRoute,
|
|
103
|
+
preview_entry_file: brief.deliverableSpec.previewEntryFile,
|
|
104
|
+
sections: brief.deliverableSpec.sections,
|
|
105
|
+
use_existing_components_first: brief.deliverableSpec.useExistingComponentsFirst,
|
|
106
|
+
},
|
|
107
|
+
update_strategy: brief.updateStrategy,
|
|
108
|
+
warnings: brief.warnings,
|
|
109
|
+
instructions: brief.instructions,
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: JSON.stringify(structuredContent, null, 2),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
structuredContent,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
server.registerTool("get_design_system", {
|
|
122
|
+
title: "Get Design System",
|
|
123
|
+
description: "Builds and writes a stable machine-readable design-system artifact for the current repo. Use this when the agent needs a durable JSON source of truth for future UI work.",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
artifactPath: z
|
|
126
|
+
.string()
|
|
127
|
+
.default(".capy/design-system.json")
|
|
128
|
+
.describe("Where to write the design-system JSON artifact relative to the repo root."),
|
|
129
|
+
mode: z
|
|
130
|
+
.enum(["build", "update"])
|
|
131
|
+
.default("build")
|
|
132
|
+
.describe("Whether this is the first artifact pass or an incremental refresh."),
|
|
133
|
+
changedFiles: z
|
|
134
|
+
.array(z.string())
|
|
135
|
+
.optional()
|
|
136
|
+
.describe("Files changed since the previous pass, when known."),
|
|
137
|
+
userGoal: z
|
|
138
|
+
.string()
|
|
139
|
+
.optional()
|
|
140
|
+
.describe("Optional end-user request to keep the artifact guidance aligned with the task."),
|
|
141
|
+
},
|
|
142
|
+
outputSchema: {
|
|
143
|
+
artifact_path: z.string(),
|
|
144
|
+
framework: z.enum([
|
|
145
|
+
"next-app-router",
|
|
146
|
+
"next-pages-router",
|
|
147
|
+
"react-router",
|
|
148
|
+
"react-no-router",
|
|
149
|
+
"unknown",
|
|
150
|
+
]),
|
|
151
|
+
routing_style: z.enum([
|
|
152
|
+
"app-router",
|
|
153
|
+
"pages-router",
|
|
154
|
+
"react-router",
|
|
155
|
+
"none",
|
|
156
|
+
"unknown",
|
|
157
|
+
]),
|
|
158
|
+
preview_route: z.string(),
|
|
159
|
+
preview_entry_file: z.string(),
|
|
160
|
+
component_count: z.number(),
|
|
161
|
+
css_variable_count: z.number(),
|
|
162
|
+
source_files: z.array(z.string()),
|
|
163
|
+
warnings: z.array(z.string()),
|
|
164
|
+
summary: z.string(),
|
|
165
|
+
},
|
|
166
|
+
}, async ({ artifactPath = ".capy/design-system.json", mode = "build", changedFiles, userGoal }) => {
|
|
167
|
+
const artifact = await writeDesignSystemArtifact(projectRoot, {
|
|
168
|
+
artifactPath,
|
|
169
|
+
mode,
|
|
170
|
+
changedFiles,
|
|
171
|
+
userGoal,
|
|
172
|
+
});
|
|
173
|
+
const structuredContent = {
|
|
174
|
+
artifact_path: artifact.meta.artifactPath,
|
|
175
|
+
framework: artifact.meta.framework,
|
|
176
|
+
routing_style: artifact.meta.routingStyle,
|
|
177
|
+
preview_route: artifact.meta.previewRoute,
|
|
178
|
+
preview_entry_file: artifact.meta.previewEntryFile,
|
|
179
|
+
component_count: artifact.components.length,
|
|
180
|
+
css_variable_count: artifact.tokens.cssVariables.length,
|
|
181
|
+
source_files: artifact.meta.sourceFiles,
|
|
182
|
+
warnings: artifact.guidance.warnings,
|
|
183
|
+
summary: artifact.guidance.summary,
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
content: [
|
|
187
|
+
{
|
|
188
|
+
type: "text",
|
|
189
|
+
text: JSON.stringify({
|
|
190
|
+
...structuredContent,
|
|
191
|
+
design_system: artifact,
|
|
192
|
+
}, null, 2),
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
structuredContent,
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
return server;
|
|
199
|
+
}
|
|
200
|
+
export async function main() {
|
|
201
|
+
const server = createServer();
|
|
202
|
+
const transport = new StdioServerTransport();
|
|
203
|
+
await server.connect(transport);
|
|
204
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export type FrameworkKind = "next-app-router" | "next-pages-router" | "react-router" | "react-no-router" | "unknown";
|
|
2
|
+
export type RoutingStyle = "app-router" | "pages-router" | "react-router" | "none" | "unknown";
|
|
3
|
+
export interface FrameworkInfo {
|
|
4
|
+
kind: FrameworkKind;
|
|
5
|
+
label: string;
|
|
6
|
+
routingStyle: RoutingStyle;
|
|
7
|
+
previewRoute: string;
|
|
8
|
+
previewEntryFile: string;
|
|
9
|
+
packageManager: "npm" | "pnpm" | "yarn" | "bun";
|
|
10
|
+
needsConfirmation: boolean;
|
|
11
|
+
confirmationMessage?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ProjectFacts {
|
|
14
|
+
framework: FrameworkKind;
|
|
15
|
+
routingStyle: RoutingStyle;
|
|
16
|
+
previewRoute: string;
|
|
17
|
+
previewEntryFile: string;
|
|
18
|
+
packageManager: FrameworkInfo["packageManager"];
|
|
19
|
+
likelyComponentDirs: string[];
|
|
20
|
+
likelyStyleFiles: string[];
|
|
21
|
+
likelyPageDirs: string[];
|
|
22
|
+
likelyUiDirs: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface InspectionStep {
|
|
25
|
+
step: number;
|
|
26
|
+
action: string;
|
|
27
|
+
targets: string[];
|
|
28
|
+
reason: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DeliverableSpec {
|
|
31
|
+
goal: string;
|
|
32
|
+
layout: "vertical-scroll";
|
|
33
|
+
allowHorizontalRows: boolean;
|
|
34
|
+
previewRoute: string;
|
|
35
|
+
previewEntryFile: string;
|
|
36
|
+
sections: string[];
|
|
37
|
+
useExistingComponentsFirst: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface PreviewBrief {
|
|
40
|
+
projectFacts: ProjectFacts;
|
|
41
|
+
inspectionPlan: InspectionStep[];
|
|
42
|
+
constraints: string[];
|
|
43
|
+
deliverableSpec: DeliverableSpec;
|
|
44
|
+
updateStrategy: string[];
|
|
45
|
+
warnings: string[];
|
|
46
|
+
instructions: string;
|
|
47
|
+
}
|
|
48
|
+
export interface DesignSystemBuildInput {
|
|
49
|
+
artifactPath?: string;
|
|
50
|
+
changedFiles?: string[];
|
|
51
|
+
mode?: "build" | "update";
|
|
52
|
+
userGoal?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface CssVariableRecord {
|
|
55
|
+
name: string;
|
|
56
|
+
value: string;
|
|
57
|
+
category: "color" | "typography" | "layout" | "other";
|
|
58
|
+
file: string;
|
|
59
|
+
line: number;
|
|
60
|
+
}
|
|
61
|
+
export interface ComponentRecord {
|
|
62
|
+
name: string;
|
|
63
|
+
path: string;
|
|
64
|
+
exports: string[];
|
|
65
|
+
kind: "primitive" | "component" | "feature" | "unknown";
|
|
66
|
+
}
|
|
67
|
+
export interface DesignSystemArtifact {
|
|
68
|
+
meta: {
|
|
69
|
+
generatedAt: string;
|
|
70
|
+
mode: "build" | "update";
|
|
71
|
+
artifactPath: string;
|
|
72
|
+
framework: FrameworkKind;
|
|
73
|
+
routingStyle: RoutingStyle;
|
|
74
|
+
previewRoute: string;
|
|
75
|
+
previewEntryFile: string;
|
|
76
|
+
packageManager: FrameworkInfo["packageManager"];
|
|
77
|
+
changedFiles: string[];
|
|
78
|
+
sourceFiles: string[];
|
|
79
|
+
};
|
|
80
|
+
directories: {
|
|
81
|
+
componentDirs: string[];
|
|
82
|
+
pageDirs: string[];
|
|
83
|
+
styleFiles: string[];
|
|
84
|
+
uiDirs: string[];
|
|
85
|
+
};
|
|
86
|
+
tokens: {
|
|
87
|
+
cssVariables: CssVariableRecord[];
|
|
88
|
+
styleFiles: string[];
|
|
89
|
+
};
|
|
90
|
+
components: ComponentRecord[];
|
|
91
|
+
preview: {
|
|
92
|
+
route: string;
|
|
93
|
+
entryFile: string;
|
|
94
|
+
sections: string[];
|
|
95
|
+
updateStrategy: string[];
|
|
96
|
+
};
|
|
97
|
+
guidance: {
|
|
98
|
+
summary: string;
|
|
99
|
+
warnings: string[];
|
|
100
|
+
instructions: string[];
|
|
101
|
+
};
|
|
102
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,11 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capy-mcp",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "MCP server that inspects a repo, returns a structured /preview brief, and writes a design-system JSON artifact for AI coding agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
5
8
|
"bin": {
|
|
6
|
-
"capy": "
|
|
9
|
+
"capy-mcp": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./server": {
|
|
17
|
+
"types": "./dist/server.d.ts",
|
|
18
|
+
"import": "./dist/server.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"dev": "tsc --watch",
|
|
24
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
25
|
+
"prepack": "npm run build",
|
|
26
|
+
"prepublishOnly": "npm test"
|
|
7
27
|
},
|
|
8
28
|
"engines": {
|
|
9
29
|
"node": ">=18"
|
|
10
|
-
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"keywords": [
|
|
35
|
+
"mcp",
|
|
36
|
+
"model-context-protocol",
|
|
37
|
+
"ai",
|
|
38
|
+
"nextjs",
|
|
39
|
+
"react",
|
|
40
|
+
"preview"
|
|
41
|
+
],
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/GithubAnant/capy.git",
|
|
45
|
+
"directory": "capy-mcp"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/GithubAnant/capy/issues"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/GithubAnant/capy/tree/main/capy-mcp",
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
53
|
+
"glob": "^11.0.1",
|
|
54
|
+
"zod": "^3.24.2"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^22.13.10",
|
|
58
|
+
"typescript": "^5.8.2"
|
|
59
|
+
},
|
|
60
|
+
"license": "MIT"
|
|
11
61
|
}
|
package/bin/cli.js
DELETED