@deppon/deppon-prd-mcp 0.1.1 → 2.5.10
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 +53 -26
- package/dist/core/canvas-handoff.d.ts +72 -0
- package/dist/core/canvas-handoff.js +238 -0
- package/dist/core/prd-generator.js +3 -9
- package/dist/core/project-store.d.ts +12 -0
- package/dist/core/project-store.js +17 -0
- package/dist/core/prototype-generator.d.ts +1 -1
- package/dist/core/prototype-generator.js +93 -89
- package/dist/core/prototype-layout.d.ts +13 -0
- package/dist/core/prototype-layout.js +219 -0
- package/dist/core/tldraw-wireframe.d.ts +11 -0
- package/dist/core/tldraw-wireframe.js +205 -0
- package/dist/core/types.d.ts +52 -0
- package/dist/core/types.js +13 -4
- package/dist/http/server.js +12 -1
- package/dist/mcp/server.js +59 -2
- package/package.json +6 -3
- package/templates/examples/README.md +8 -8
- package/templates/examples/app-shell-navigation/layout-spec.md +21 -21
- package/templates/examples/app-shell-navigation/prd.md +56 -56
- package/templates/examples/backend-list/prd.md +32 -32
- package/templates/examples/data-dashboard/prd.md +46 -46
- package/templates/examples/form-edit/prd.md +33 -33
- package/templates/examples/form-preview/prd.md +21 -21
- package/templates/examples/user-frontend/prd.md +19 -19
- package/web/app.js +80 -45
- package/web/index.html +2 -2
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
const WIDGET_COLORS = {
|
|
3
|
+
ShellSidebar: 'light-blue',
|
|
4
|
+
PageHeader: 'grey',
|
|
5
|
+
FilterBar: 'yellow',
|
|
6
|
+
DataTable: 'light-green',
|
|
7
|
+
};
|
|
8
|
+
function shapeId() {
|
|
9
|
+
return `shape:${crypto.randomBytes(4).toString('hex')}`;
|
|
10
|
+
}
|
|
11
|
+
function pageId() {
|
|
12
|
+
return `page:${crypto.randomBytes(4).toString('hex')}`;
|
|
13
|
+
}
|
|
14
|
+
function indexAt(i) {
|
|
15
|
+
return `a${i + 1}`;
|
|
16
|
+
}
|
|
17
|
+
function geoShape(page, idx, x, y, w, h, text, color) {
|
|
18
|
+
return {
|
|
19
|
+
id: shapeId(),
|
|
20
|
+
typeName: 'shape',
|
|
21
|
+
type: 'geo',
|
|
22
|
+
x,
|
|
23
|
+
y,
|
|
24
|
+
rotation: 0,
|
|
25
|
+
isLocked: false,
|
|
26
|
+
opacity: 1,
|
|
27
|
+
parentId: page,
|
|
28
|
+
index: indexAt(idx),
|
|
29
|
+
meta: {},
|
|
30
|
+
props: {
|
|
31
|
+
w,
|
|
32
|
+
h,
|
|
33
|
+
geo: 'rectangle',
|
|
34
|
+
color,
|
|
35
|
+
fill: 'semi',
|
|
36
|
+
text,
|
|
37
|
+
dash: 'draw',
|
|
38
|
+
size: 'm',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function textShape(page, idx, x, y, text) {
|
|
43
|
+
return {
|
|
44
|
+
id: shapeId(),
|
|
45
|
+
typeName: 'shape',
|
|
46
|
+
type: 'text',
|
|
47
|
+
x,
|
|
48
|
+
y,
|
|
49
|
+
rotation: 0,
|
|
50
|
+
isLocked: false,
|
|
51
|
+
opacity: 1,
|
|
52
|
+
parentId: page,
|
|
53
|
+
index: indexAt(idx),
|
|
54
|
+
meta: {},
|
|
55
|
+
props: {
|
|
56
|
+
color: 'black',
|
|
57
|
+
size: 's',
|
|
58
|
+
w: 280,
|
|
59
|
+
text,
|
|
60
|
+
font: 'draw',
|
|
61
|
+
autoSize: true,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function buildTldrawWireframeDocument(project, widgets) {
|
|
66
|
+
const pid = pageId();
|
|
67
|
+
const records = [
|
|
68
|
+
{
|
|
69
|
+
id: 'document:document',
|
|
70
|
+
typeName: 'document',
|
|
71
|
+
gridSize: 10,
|
|
72
|
+
name: `${project.pageName} · 线框`,
|
|
73
|
+
meta: { slug: project.slug, source: 'deppon-prd-mcp' },
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: pid,
|
|
77
|
+
typeName: 'page',
|
|
78
|
+
name: project.pageName,
|
|
79
|
+
index: 'a1',
|
|
80
|
+
meta: {},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: `instance_page_state:${pid}`,
|
|
84
|
+
typeName: 'instance_page_state',
|
|
85
|
+
pageId: pid,
|
|
86
|
+
selectedShapeIds: [],
|
|
87
|
+
hintingShapeIds: [],
|
|
88
|
+
erasingShapeIds: [],
|
|
89
|
+
hoveredShapeId: null,
|
|
90
|
+
editingShapeId: null,
|
|
91
|
+
croppingShapeId: null,
|
|
92
|
+
focusedGroupId: null,
|
|
93
|
+
meta: {},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: `camera:${pid}`,
|
|
97
|
+
typeName: 'camera',
|
|
98
|
+
x: -20,
|
|
99
|
+
y: -20,
|
|
100
|
+
z: 0.85,
|
|
101
|
+
meta: {},
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
let shapeIdx = 0;
|
|
105
|
+
records.push(geoShape(pid, shapeIdx++, 0, 0, 1440, 900, `${project.pageName} · B端线框 1440×900`, 'black'));
|
|
106
|
+
for (const widget of widgets) {
|
|
107
|
+
const color = WIDGET_COLORS[widget.type] || 'violet';
|
|
108
|
+
const label = `${widget.type}${widget.props.menuPath || widget.props.title || widget.props.activeItem
|
|
109
|
+
? `\n${String(widget.props.menuPath || widget.props.title || widget.props.activeItem)}`
|
|
110
|
+
: ''}`;
|
|
111
|
+
records.push(geoShape(pid, shapeIdx++, widget.x, widget.y, widget.w, widget.h, label.slice(0, 120), color));
|
|
112
|
+
}
|
|
113
|
+
if (project.filters.length > 0) {
|
|
114
|
+
const filterWidget = widgets.find(w => w.type === 'FilterBar');
|
|
115
|
+
if (filterWidget) {
|
|
116
|
+
const cols = Math.min(project.filters.length, 4);
|
|
117
|
+
const cellW = Math.floor((filterWidget.w - 32) / cols);
|
|
118
|
+
project.filters.forEach((f, i) => {
|
|
119
|
+
const col = i % cols;
|
|
120
|
+
const row = Math.floor(i / cols);
|
|
121
|
+
records.push(geoShape(pid, shapeIdx++, filterWidget.x + 16 + col * cellW, filterWidget.y + 48 + row * 44, cellW - 12, 36, f.name, 'light-violet'));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (project.columns.length > 0) {
|
|
126
|
+
const table = widgets.find(w => w.type === 'DataTable');
|
|
127
|
+
if (table) {
|
|
128
|
+
const colW = Math.floor(table.w / (project.columns.length + 1));
|
|
129
|
+
project.columns.forEach((c, i) => {
|
|
130
|
+
records.push(textShape(pid, shapeIdx++, table.x + 8 + i * colW, table.y + 36, c.name));
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
records.push(textShape(pid, shapeIdx++, 240, 16, `PRD 线框 · ${project.pageName}\n${project.menuPath}\n打开:tldraw.com 或 VS Code tldraw 扩展`));
|
|
135
|
+
return {
|
|
136
|
+
document: records[0],
|
|
137
|
+
pageId: pid,
|
|
138
|
+
records,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function buildTldrawFile(project, widgets) {
|
|
142
|
+
const { records } = buildTldrawWireframeDocument(project, widgets);
|
|
143
|
+
return {
|
|
144
|
+
tldrawFileFormatVersion: 1,
|
|
145
|
+
schema: {
|
|
146
|
+
schemaVersion: 2,
|
|
147
|
+
sequences: {
|
|
148
|
+
'com.tldraw.store': 4,
|
|
149
|
+
'com.tldraw.asset': 1,
|
|
150
|
+
'com.tldraw.camera': 1,
|
|
151
|
+
'com.tldraw.document': 2,
|
|
152
|
+
'com.tldraw.instance': 25,
|
|
153
|
+
'com.tldraw.instance_page_state': 5,
|
|
154
|
+
'com.tldraw.page': 1,
|
|
155
|
+
'com.tldraw.pointer': 1,
|
|
156
|
+
'com.tldraw.shape': 4,
|
|
157
|
+
'com.tldraw.shape.geo': 10,
|
|
158
|
+
'com.tldraw.shape.text': 3,
|
|
159
|
+
'com.tldraw.shape.note': 9,
|
|
160
|
+
'com.tldraw.shape.arrow': 6,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
records,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export function buildTldrawPrompt(project, handoff, tldrRelativePath) {
|
|
167
|
+
const widgetLines = handoff.widgets.map(w => `- ${w.type} (${w.x},${w.y}) ${w.w}×${w.h}`).join('\n');
|
|
168
|
+
return `# tldraw 线框任务:${project.pageName}
|
|
169
|
+
|
|
170
|
+
基于 deppon-prd-mcp 已生成 PRD,在 tldraw 中绘制 **B 端后台线框**(低精度布局,用于评审与流程说明)。
|
|
171
|
+
|
|
172
|
+
## 文件
|
|
173
|
+
- 相对路径(TLDRAW_DIR 下):\`${tldrRelativePath}\`
|
|
174
|
+
- 已预生成线框:\`${handoff.paths.tldrawWireframe || `<slug>/wireframe.tldr`}\`
|
|
175
|
+
- 可用 tldraw MCP 继续 \`tldraw_add_shape\` / \`tldraw_update_shape\` 微调
|
|
176
|
+
|
|
177
|
+
## 页面背景
|
|
178
|
+
${project.background}
|
|
179
|
+
|
|
180
|
+
## 布局区块
|
|
181
|
+
${widgetLines}
|
|
182
|
+
|
|
183
|
+
## 筛选项
|
|
184
|
+
${project.filters.map(f => `- ${f.name}(${f.type})`).join('\n') || '(无)'}
|
|
185
|
+
|
|
186
|
+
## 表格列
|
|
187
|
+
${project.columns.map(c => `- ${c.name}`).join('\n')}
|
|
188
|
+
|
|
189
|
+
## 工具栏 / 行操作
|
|
190
|
+
- 工具栏:${project.toolbarButtons.join('、')}
|
|
191
|
+
- 行操作:${project.rowActions.join('、')}
|
|
192
|
+
|
|
193
|
+
## MCP 操作建议
|
|
194
|
+
1. \`tldraw_read\` 读取 \`${tldrRelativePath}\` 查看现有线框
|
|
195
|
+
2. 按需 \`tldraw_add_shape\` 补充流程箭头(type: arrow)或备注(type: note)
|
|
196
|
+
3. 高保真视觉稿请用 Penpot/Reframe;tldraw 专注 **线框与流程**
|
|
197
|
+
|
|
198
|
+
## 打开方式
|
|
199
|
+
- [tldraw.com](https://www.tldraw.com/) → File → Open → 选择 wireframe.tldr
|
|
200
|
+
- VS Code 安装 tldraw 扩展后直接打开文件
|
|
201
|
+
|
|
202
|
+
## 关联 PRD
|
|
203
|
+
${handoff.paths.prd}
|
|
204
|
+
`;
|
|
205
|
+
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -56,6 +56,25 @@ export declare const PermissionRowSchema: z.ZodObject<{
|
|
|
56
56
|
scope?: string | undefined;
|
|
57
57
|
dataScope?: string | undefined;
|
|
58
58
|
}>;
|
|
59
|
+
export declare const PrototypeLayoutSchema: z.ZodObject<{
|
|
60
|
+
blockOrder: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
61
|
+
filterOrder: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
62
|
+
columnOrder: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
63
|
+
toolbarOrder: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
64
|
+
hiddenBlocks: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
65
|
+
}, "strip", z.ZodTypeAny, {
|
|
66
|
+
blockOrder?: string[] | undefined;
|
|
67
|
+
filterOrder?: number[] | undefined;
|
|
68
|
+
columnOrder?: number[] | undefined;
|
|
69
|
+
toolbarOrder?: number[] | undefined;
|
|
70
|
+
hiddenBlocks?: string[] | undefined;
|
|
71
|
+
}, {
|
|
72
|
+
blockOrder?: string[] | undefined;
|
|
73
|
+
filterOrder?: number[] | undefined;
|
|
74
|
+
columnOrder?: number[] | undefined;
|
|
75
|
+
toolbarOrder?: number[] | undefined;
|
|
76
|
+
hiddenBlocks?: string[] | undefined;
|
|
77
|
+
}>;
|
|
59
78
|
export declare const PrdProjectSchema: z.ZodObject<{
|
|
60
79
|
slug: z.ZodString;
|
|
61
80
|
pageName: z.ZodString;
|
|
@@ -124,6 +143,25 @@ export declare const PrdProjectSchema: z.ZodObject<{
|
|
|
124
143
|
createButtonLabel: z.ZodOptional<z.ZodString>;
|
|
125
144
|
enableCrud: z.ZodDefault<z.ZodBoolean>;
|
|
126
145
|
mockRows: z.ZodDefault<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodString>, "many">>;
|
|
146
|
+
layout: z.ZodOptional<z.ZodObject<{
|
|
147
|
+
blockOrder: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
148
|
+
filterOrder: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
149
|
+
columnOrder: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
150
|
+
toolbarOrder: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
151
|
+
hiddenBlocks: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
152
|
+
}, "strip", z.ZodTypeAny, {
|
|
153
|
+
blockOrder?: string[] | undefined;
|
|
154
|
+
filterOrder?: number[] | undefined;
|
|
155
|
+
columnOrder?: number[] | undefined;
|
|
156
|
+
toolbarOrder?: number[] | undefined;
|
|
157
|
+
hiddenBlocks?: string[] | undefined;
|
|
158
|
+
}, {
|
|
159
|
+
blockOrder?: string[] | undefined;
|
|
160
|
+
filterOrder?: number[] | undefined;
|
|
161
|
+
columnOrder?: number[] | undefined;
|
|
162
|
+
toolbarOrder?: number[] | undefined;
|
|
163
|
+
hiddenBlocks?: string[] | undefined;
|
|
164
|
+
}>>;
|
|
127
165
|
}, "strip", z.ZodTypeAny, {
|
|
128
166
|
slug: string;
|
|
129
167
|
pageName: string;
|
|
@@ -158,6 +196,13 @@ export declare const PrdProjectSchema: z.ZodObject<{
|
|
|
158
196
|
mockRows: Record<string, string>[];
|
|
159
197
|
batchDate?: string | undefined;
|
|
160
198
|
createButtonLabel?: string | undefined;
|
|
199
|
+
layout?: {
|
|
200
|
+
blockOrder?: string[] | undefined;
|
|
201
|
+
filterOrder?: number[] | undefined;
|
|
202
|
+
columnOrder?: number[] | undefined;
|
|
203
|
+
toolbarOrder?: number[] | undefined;
|
|
204
|
+
hiddenBlocks?: string[] | undefined;
|
|
205
|
+
} | undefined;
|
|
161
206
|
}, {
|
|
162
207
|
slug: string;
|
|
163
208
|
pageName: string;
|
|
@@ -192,6 +237,13 @@ export declare const PrdProjectSchema: z.ZodObject<{
|
|
|
192
237
|
createButtonLabel?: string | undefined;
|
|
193
238
|
enableCrud?: boolean | undefined;
|
|
194
239
|
mockRows?: Record<string, string>[] | undefined;
|
|
240
|
+
layout?: {
|
|
241
|
+
blockOrder?: string[] | undefined;
|
|
242
|
+
filterOrder?: number[] | undefined;
|
|
243
|
+
columnOrder?: number[] | undefined;
|
|
244
|
+
toolbarOrder?: number[] | undefined;
|
|
245
|
+
hiddenBlocks?: string[] | undefined;
|
|
246
|
+
} | undefined;
|
|
195
247
|
}>;
|
|
196
248
|
export type PrdProject = z.infer<typeof PrdProjectSchema>;
|
|
197
249
|
export declare const PAGE_TYPE_LABELS: Record<PageType, string>;
|
package/dist/core/types.js
CHANGED
|
@@ -28,11 +28,21 @@ export const PermissionRowSchema = z.object({
|
|
|
28
28
|
scope: z.string().default(''),
|
|
29
29
|
dataScope: z.string().default('全量'),
|
|
30
30
|
});
|
|
31
|
+
export const PrototypeLayoutSchema = z.object({
|
|
32
|
+
blockOrder: z.array(z.string()).optional(),
|
|
33
|
+
filterOrder: z.array(z.number()).optional(),
|
|
34
|
+
columnOrder: z.array(z.number()).optional(),
|
|
35
|
+
toolbarOrder: z.array(z.number()).optional(),
|
|
36
|
+
hiddenBlocks: z.array(z.string()).optional(),
|
|
37
|
+
});
|
|
31
38
|
export const PrdProjectSchema = z.object({
|
|
32
39
|
slug: z.string().min(1),
|
|
33
40
|
pageName: z.string().min(1),
|
|
34
41
|
pageType: PageTypeSchema.default('list-crud'),
|
|
35
|
-
batchDate: z
|
|
42
|
+
batchDate: z
|
|
43
|
+
.string()
|
|
44
|
+
.regex(/^\d{8}$/)
|
|
45
|
+
.optional(),
|
|
36
46
|
useDpItTitle: z.boolean().default(true),
|
|
37
47
|
background: z.string().min(1),
|
|
38
48
|
features: z.array(FeatureRowSchema).default([]),
|
|
@@ -44,9 +54,8 @@ export const PrdProjectSchema = z.object({
|
|
|
44
54
|
rowActions: z.array(z.string()).default(['查看', '编辑', '删除']),
|
|
45
55
|
createButtonLabel: z.string().optional(),
|
|
46
56
|
enableCrud: z.boolean().default(true),
|
|
47
|
-
mockRows: z
|
|
48
|
-
|
|
49
|
-
.default([]),
|
|
57
|
+
mockRows: z.array(z.record(z.string())).default([]),
|
|
58
|
+
layout: PrototypeLayoutSchema.optional(),
|
|
50
59
|
});
|
|
51
60
|
export const PAGE_TYPE_LABELS = {
|
|
52
61
|
'list-crud': '后台列表 + CRUD',
|
package/dist/http/server.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { getHttpHost, getHttpPort, getPrototypesRoot, getWebRoot } from '../config.js';
|
|
5
5
|
import { generatePrdMarkdown } from '../core/prd-generator.js';
|
|
6
6
|
import { generatePrototypeHtml } from '../core/prototype-generator.js';
|
|
7
|
-
import { applyOverrides, listProjects, loadProject, readProjectFiles, saveProject, } from '../core/project-store.js';
|
|
7
|
+
import { applyOverrides, listProjects, loadProject, readProjectFiles, saveDesignLayout, saveProject, } from '../core/project-store.js';
|
|
8
8
|
import { createDefaultProject, PAGE_TYPE_LABELS, PrdProjectSchema } from '../core/types.js';
|
|
9
9
|
let started = false;
|
|
10
10
|
export async function startHttpServer() {
|
|
@@ -87,6 +87,17 @@ export async function startHttpServer() {
|
|
|
87
87
|
res.status(400).json({ error: e instanceof Error ? e.message : String(e) });
|
|
88
88
|
}
|
|
89
89
|
});
|
|
90
|
+
app.post('/api/projects/:slug/design', (req, res) => {
|
|
91
|
+
try {
|
|
92
|
+
const updated = saveDesignLayout(req.params.slug, req.body || {});
|
|
93
|
+
if (!updated)
|
|
94
|
+
return res.status(404).json({ error: '项目不存在' });
|
|
95
|
+
res.json({ ok: true, project: updated, slug: updated.slug });
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
res.status(400).json({ error: e instanceof Error ? e.message : String(e) });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
90
101
|
app.get('/api/projects/:slug/prototype', (req, res) => {
|
|
91
102
|
const files = readProjectFiles(req.params.slug);
|
|
92
103
|
if (!files)
|
package/dist/mcp/server.js
CHANGED
|
@@ -4,8 +4,9 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
|
|
|
4
4
|
import { getHttpBaseUrl } from '../config.js';
|
|
5
5
|
import { generatePrdMarkdown } from '../core/prd-generator.js';
|
|
6
6
|
import { generatePrototypeHtml } from '../core/prototype-generator.js';
|
|
7
|
-
import { applyOverrides, listProjects, loadProject, readProjectFiles, saveProject
|
|
8
|
-
import { createDefaultProject, PAGE_TYPE_LABELS, PageTypeSchema, PrdProjectSchema
|
|
7
|
+
import { applyOverrides, listProjects, loadProject, readProjectFiles, saveProject } from '../core/project-store.js';
|
|
8
|
+
import { createDefaultProject, PAGE_TYPE_LABELS, PageTypeSchema, PrdProjectSchema } from '../core/types.js';
|
|
9
|
+
import { saveCanvasHandoff } from '../core/canvas-handoff.js';
|
|
9
10
|
import { startHttpServer } from '../http/server.js';
|
|
10
11
|
export async function startMcpServer() {
|
|
11
12
|
await startHttpServer();
|
|
@@ -79,6 +80,22 @@ export async function startMcpServer() {
|
|
|
79
80
|
},
|
|
80
81
|
},
|
|
81
82
|
},
|
|
83
|
+
{
|
|
84
|
+
name: 'export_canvas_handoff',
|
|
85
|
+
description: 'PRD 项目导出 canvas-handoff.json + penpot/reframe/tldraw Prompt + wireframe.tldr,供外部 MCP 绘制原型(PRD 仍由 deppon-prd-mcp 管理)。',
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
slug: { type: 'string', description: '项目 slug' },
|
|
90
|
+
target: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
enum: ['all', 'both', 'penpot', 'reframe', 'tldraw'],
|
|
93
|
+
description: 'handoff 目标,默认 all(含 tldraw 线框)',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
required: ['slug'],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
82
99
|
],
|
|
83
100
|
}));
|
|
84
101
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -181,6 +198,46 @@ export async function startMcpServer() {
|
|
|
181
198
|
],
|
|
182
199
|
};
|
|
183
200
|
}
|
|
201
|
+
if (name === 'export_canvas_handoff') {
|
|
202
|
+
const slug = String(input.slug || '');
|
|
203
|
+
const target = String(input.target || 'all');
|
|
204
|
+
const project = loadProject(slug);
|
|
205
|
+
if (!project)
|
|
206
|
+
throw new Error(`项目不存在: ${slug}`);
|
|
207
|
+
const result = saveCanvasHandoff(project);
|
|
208
|
+
const referenceUrl = `${getHttpBaseUrl()}/projects/${slug}/prototype.html`;
|
|
209
|
+
const tldrawWireframeUrl = `${getHttpBaseUrl()}/projects/${slug}/wireframe.tldr`;
|
|
210
|
+
return {
|
|
211
|
+
content: [
|
|
212
|
+
{
|
|
213
|
+
type: 'text',
|
|
214
|
+
text: JSON.stringify({
|
|
215
|
+
ok: true,
|
|
216
|
+
slug,
|
|
217
|
+
target,
|
|
218
|
+
handoffPath: result.handoffPath,
|
|
219
|
+
penpotPromptPath: result.penpotPromptPath,
|
|
220
|
+
reframePromptPath: result.reframePromptPath,
|
|
221
|
+
tldrawPromptPath: result.tldrawPromptPath,
|
|
222
|
+
tldrawWireframePath: result.tldrawWireframePath,
|
|
223
|
+
tldrawWireframeUrl,
|
|
224
|
+
referencePrototypeUrl: referenceUrl,
|
|
225
|
+
widgetCount: result.handoff.widgets.length,
|
|
226
|
+
penpotPromptPreview: result.handoff.penpot.prompt.slice(0, 400),
|
|
227
|
+
reframePromptPreview: result.handoff.reframe.prompt.slice(0, 400),
|
|
228
|
+
tldrawPromptPreview: result.handoff.tldraw.prompt.slice(0, 400),
|
|
229
|
+
nextSteps: result.nextSteps,
|
|
230
|
+
cursorWorkflow: [
|
|
231
|
+
'1. deppon-prd: generate_prd → export_canvas_handoff',
|
|
232
|
+
'2. tldraw: 打开 wireframe.tldr,Agent 读 tldraw-prompt.md 用 tldraw MCP 补充线框',
|
|
233
|
+
'3. penpot: yarn penpot:mcp,Agent 读 penpot-prompt.md',
|
|
234
|
+
'4. reframe: yarn reframe:platform,Agent 读 reframe-prompt.md',
|
|
235
|
+
],
|
|
236
|
+
}, null, 2),
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
184
241
|
throw new Error(`Unknown tool: ${name}`);
|
|
185
242
|
}
|
|
186
243
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deppon/deppon-prd-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"mcpName": "io.github.
|
|
3
|
+
"version": "2.5.10",
|
|
4
|
+
"mcpName": "io.github.chriswong1103/deppon-prd-mcp",
|
|
5
5
|
"description": "私有化 PRD 生成 + 交互原型 MCP 服务(基于 deppon-prd-generator skill)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
@@ -30,7 +30,10 @@
|
|
|
30
30
|
"dev:web": "npm run build && node dist/cli.js --web-only",
|
|
31
31
|
"prepack": "npm run build",
|
|
32
32
|
"publish:auto": "npm publish",
|
|
33
|
-
"publish:registry": "mcp-publisher publish"
|
|
33
|
+
"publish:registry": "mcp-publisher publish",
|
|
34
|
+
"penpot:mcp": "bash scripts/start-penpot-mcp.sh",
|
|
35
|
+
"reframe:setup": "bash scripts/setup-reframe.sh",
|
|
36
|
+
"reframe:platform": "cd \"$HOME/.deppon/reframe\" && npm start"
|
|
34
37
|
},
|
|
35
38
|
"dependencies": {
|
|
36
39
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
本目录存放**已完成的对照示例**(非业务仓库的 `src/prototypes/` 交付物),供生成 PRD / 可选 `prototype.html` 时对齐**章节结构、表格粒度、标注与 `prd.md` 联动(`fetch` + `marked`)**。各子目录内 `prd.md` 文首含指向**同目录** `prototype.html` 的 Markdown 相对链接(与 `SKILL.md`「PRD 与原型互链」一致);正文与原型 UI 默认**简体中文**。
|
|
4
4
|
|
|
5
|
-
| 子目录
|
|
6
|
-
|
|
|
7
|
-
| `list-crud/`
|
|
8
|
-
| `backend-list/`
|
|
9
|
-
| `form-edit/`
|
|
10
|
-
| `form-preview/`
|
|
11
|
-
| `data-dashboard/`
|
|
12
|
-
| `user-frontend/`
|
|
5
|
+
| 子目录 | 对应模板文件 | 页面类型 / 场景 |
|
|
6
|
+
| ----------------------- | ----------------------------------------------- | --------------------------------------------------------------- |
|
|
7
|
+
| `list-crud/` | `template/backend-list-prd-template.md` | 后台列表 **+ 完整 CRUD**(筛选/分页/详情/表单/删除) |
|
|
8
|
+
| `backend-list/` | `template/backend-list-prd-template.md` | 后台 **纯列表**(筛选 + 表 + 分页,无弹层 CRUD) |
|
|
9
|
+
| `form-edit/` | `template/backend-form-edit-prd-template.md` | 后台 **整页表单编辑**(字段/联动/保存取消) |
|
|
10
|
+
| `form-preview/` | `template/backend-form-preview-prd-template.md` | 后台 **只读详情 / 预览** |
|
|
11
|
+
| `data-dashboard/` | `template/data-prd-template.md` | **数据看板**(筛选 + 卡 + 图 + 表 + 指标总览) |
|
|
12
|
+
| `user-frontend/` | `template/user-frontend-prd-template.md` | **用户前台 / C 端**活动页 |
|
|
13
13
|
| `app-shell-navigation/` | `template/app-shell-navigation-prd-template.md` | **管理台壳层 + 多页文档中心**(侧栏/顶栏/栅格 + `pages/` 互跳) |
|
|
14
14
|
|
|
15
15
|
**打开原型**:各 `prototype.html` 须通过**本地 HTTP**访问同目录,以便 `fetch('prd.md')`;勿依赖 `file://`。**`app-shell-navigation/`** 还须从该子目录打开,保证 iframe 内 `pages/*.html` 相对路径可用。
|
|
@@ -4,37 +4,37 @@
|
|
|
4
4
|
|
|
5
5
|
## 1. 整体结构(左右 + 顶)
|
|
6
6
|
|
|
7
|
-
| 区域
|
|
8
|
-
|
|
|
9
|
-
| 侧栏
|
|
10
|
-
| 顶栏
|
|
11
|
-
| 主工作区 | 顶栏下方、侧栏右侧 | 内嵌 **iframe** 或路由 `router-view`
|
|
7
|
+
| 区域 | 位置 | 说明 |
|
|
8
|
+
| -------- | ------------------ | --------------------------------------- |
|
|
9
|
+
| 侧栏 | 左侧固定 | 主导航;展开宽 **180px**,收起 **56px** |
|
|
10
|
+
| 顶栏 | 主区上方 | 高 **56px**;Logo/标题左,工具右 |
|
|
11
|
+
| 主工作区 | 顶栏下方、侧栏右侧 | 内嵌 **iframe** 或路由 `router-view` |
|
|
12
12
|
|
|
13
13
|
## 2. 间距(与视觉稿一致时可照抄)
|
|
14
14
|
|
|
15
|
-
| 名称
|
|
16
|
-
|
|
|
17
|
-
| 侧栏—主区
|
|
18
|
-
| 顶栏—内容
|
|
19
|
-
| 主区内块间距 | **16px** | 卡片/区块之间
|
|
20
|
-
| 页面底边距
|
|
15
|
+
| 名称 | 数值 | 用途 |
|
|
16
|
+
| ------------ | -------- | --------------------- |
|
|
17
|
+
| 侧栏—主区 | **16px** | 水平间隙 |
|
|
18
|
+
| 顶栏—内容 | **16px** | 顶栏底到主区顶 |
|
|
19
|
+
| 主区内块间距 | **16px** | 卡片/区块之间 |
|
|
20
|
+
| 页面底边距 | **24px** | 主区 `padding-bottom` |
|
|
21
21
|
|
|
22
22
|
## 3. 24 栅格(主区内逻辑分栏)
|
|
23
23
|
|
|
24
|
-
| 区块
|
|
25
|
-
|
|
|
26
|
-
| 顶通栏 | **24 / 24**
|
|
27
|
-
| 下区左 | **16 / 24**(约 2/3) | 列表、大图表
|
|
28
|
-
| 下区右 | **8 / 24**(约 1/3)
|
|
24
|
+
| 区块 | 建议占位 | 说明 |
|
|
25
|
+
| ------ | --------------------- | ------------------- |
|
|
26
|
+
| 顶通栏 | **24 / 24** | 全宽 KPI / 筛选横条 |
|
|
27
|
+
| 下区左 | **16 / 24**(约 2/3) | 列表、大图表 |
|
|
28
|
+
| 下区右 | **8 / 24**(约 1/3) | 辅信息、快捷入口 |
|
|
29
29
|
|
|
30
30
|
## 4. 侧栏导航层级(交互摘要)
|
|
31
31
|
|
|
32
|
-
| 层级 | 行高参考 | 交互
|
|
33
|
-
| ---- | -------- |
|
|
32
|
+
| 层级 | 行高参考 | 交互 |
|
|
33
|
+
| ---- | -------- | ----------------------------------- |
|
|
34
34
|
| 一级 | **52px** | 图标 + 文案 + chevron;点击展开二级 |
|
|
35
|
-
| 二级 | 略缩进
|
|
36
|
-
| 三级 | 飞层卡片 | 悬停二级项右侧浮出;点击进子页
|
|
37
|
-
| 收起 | 宽 56px
|
|
35
|
+
| 二级 | 略缩进 | 无图标或弱图标 |
|
|
36
|
+
| 三级 | 飞层卡片 | 悬停二级项右侧浮出;点击进子页 |
|
|
37
|
+
| 收起 | 宽 56px | 仅图标 + Tooltip |
|
|
38
38
|
|
|
39
39
|
## 5. 多页文档中心文件约定
|
|
40
40
|
|