@morya-ui/mcp 0.1.1

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createToolHandlers } from '../tools.js';
3
+ import { countCatalogResourceTemplates, countCatalogResources } from '../resources.js';
4
+ describe('@morya-ui/mcp resources', () => {
5
+ const handlers = createToolHandlers();
6
+ it('registers static resources and resource templates', () => {
7
+ expect(countCatalogResources(handlers.catalog)).toBeGreaterThan(100);
8
+ expect(countCatalogResourceTemplates()).toBe(3);
9
+ });
10
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,109 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createToolHandlers } from '../tools.js';
3
+ function read(result) {
4
+ return JSON.parse(result.content[0].text);
5
+ }
6
+ describe('@morya-ui/mcp handlers', () => {
7
+ const handlers = createToolHandlers();
8
+ it('returns complete pagination metadata for component lists', () => {
9
+ const result = read(handlers.list({ kind: 'components', limit: 5, offset: 5 }));
10
+ expect(result.total).toBeGreaterThan(5);
11
+ expect(result.count).toBe(5);
12
+ expect(result.offset).toBe(5);
13
+ expect(result.limit).toBe(5);
14
+ expect(result.has_more).toBe(true);
15
+ expect(result.next_offset).toBe(10);
16
+ });
17
+ it('paginates categories instead of returning the complete collection', () => {
18
+ const result = read(handlers.list({ kind: 'categories', limit: 1, offset: 1 }));
19
+ expect(result.items).toHaveLength(1);
20
+ expect(result.count).toBe(1);
21
+ });
22
+ it('resolves documented aliases when reading component API', () => {
23
+ const result = read(handlers.getComponent({ component: '数据表格', includeApi: true }));
24
+ expect(result.id).toBe('Table');
25
+ expect(result.exportName).toBe('MTable');
26
+ });
27
+ it('paginates component examples and reports API coverage', () => {
28
+ const first = read(handlers.getComponent({ component: 'Table', detail: 'full', examplesLimit: 1 }));
29
+ expect(first.examples).toHaveLength(1);
30
+ expect(first.exampleCount).toBeGreaterThan(1);
31
+ expect(first.examplesOffset).toBe(0);
32
+ expect(first.examplesLimit).toBe(1);
33
+ expect(first.hasMoreExamples).toBe(true);
34
+ expect(first.nextExamplesOffset).toBe(1);
35
+ expect(first.apiCoverage.props.total).toBeGreaterThan(0);
36
+ const second = read(handlers.getComponent({ component: 'Table', detail: 'full', examplesLimit: 1, examplesOffset: 1 }));
37
+ expect(second.examplesOffset).toBe(1);
38
+ expect(second.examples[0]?.id).not.toBe(first.examples[0]?.id);
39
+ });
40
+ it('reports unknown props without rejecting valid props', () => {
41
+ const result = read(handlers.validateUsage({
42
+ component: 'Button',
43
+ code: '<MButton label="Save" severity="danger" foo="bar" />',
44
+ }));
45
+ expect(result.ok).toBe(false);
46
+ expect(result.issues).toEqual([
47
+ expect.objectContaining({ type: 'unknown-prop' }),
48
+ ]);
49
+ expect(result.issues[0].message).toContain('foo');
50
+ });
51
+ it('flags icon-only buttons that put icons in the default slot', () => {
52
+ const result = read(handlers.validateUsage({
53
+ component: 'Button',
54
+ code: '<MButton icon-only aria-label="Add"><Plus /></MButton>',
55
+ }));
56
+ expect(result.ok).toBe(false);
57
+ expect(result.issues).toEqual(expect.arrayContaining([
58
+ expect.objectContaining({ type: 'icon-only-missing-icon' }),
59
+ expect.objectContaining({ type: 'icon-only-default-slot' }),
60
+ ]));
61
+ });
62
+ it('accepts icon-only buttons with icon prop', () => {
63
+ const result = read(handlers.validateUsage({
64
+ component: 'Button',
65
+ code: '<MButton icon="plus" icon-only aria-label="Add" />',
66
+ }));
67
+ expect(result.ok).toBe(true);
68
+ });
69
+ it('recommends a page pattern from product intent', () => {
70
+ const result = read(handlers.recommendPage({
71
+ intent: '油井管理列表',
72
+ pageType: 'list',
73
+ features: ['筛选', '分页'],
74
+ }));
75
+ expect(result.matchedPattern).toBe('admin-list');
76
+ });
77
+ it('returns a dashboard scaffold when includeScaffold is true', () => {
78
+ const result = read(handlers.recommendPage({
79
+ intent: '生产监控仪表盘',
80
+ pageType: 'dashboard',
81
+ includeScaffold: true,
82
+ }));
83
+ expect(result.matchedPattern).toBe('dashboard');
84
+ expect(result.scaffold.files.component).toContain('MGrid');
85
+ expect(result.scaffold.files.component).toContain('MSkeleton');
86
+ });
87
+ it('lists component decision guides when query is omitted', () => {
88
+ const result = read(handlers.recommendComponent({ limit: 5 }));
89
+ expect(result.kind).toBe('decisions');
90
+ expect(result.items.length).toBeGreaterThan(0);
91
+ expect(result.items[0]?.id).toBeTruthy();
92
+ });
93
+ it('reads a decision guide by id', () => {
94
+ const result = read(handlers.recommendComponent({ decision: 'overlay-choice' }));
95
+ expect(result.id).toBe('overlay-choice');
96
+ expect(result.options.some((option) => option.component === 'Drawer')).toBe(true);
97
+ });
98
+ it('exposes catalog health in version metadata', () => {
99
+ const result = read(handlers.version());
100
+ expect(result.health.ok).toBe(true);
101
+ expect(result.health.patternReferences).toEqual([]);
102
+ expect(result.counts.patterns).toBeGreaterThan(0);
103
+ expect(result.counts.decisions).toBeGreaterThan(0);
104
+ expect(result.counts.resources).toBeGreaterThan(100);
105
+ expect(result.counts.resourceTemplates).toBe(3);
106
+ expect(result.tools).toHaveLength(13);
107
+ expect(result.tools).not.toContain('create_page');
108
+ });
109
+ });
@@ -0,0 +1,97 @@
1
+ export type Locale = 'zh-CN' | 'en-US';
2
+ export interface ApiProp {
3
+ name: string;
4
+ type: string;
5
+ default?: string;
6
+ description: string;
7
+ }
8
+ export interface ApiEvent {
9
+ name: string;
10
+ payload?: string;
11
+ description: string;
12
+ }
13
+ export interface ApiSlot {
14
+ name: string;
15
+ description: string;
16
+ }
17
+ export interface ApiMethod {
18
+ name: string;
19
+ type?: string;
20
+ description: string;
21
+ }
22
+ export interface ExampleItem {
23
+ id: string;
24
+ section: string;
25
+ sectionId: string;
26
+ lang: string;
27
+ preview: boolean;
28
+ code: string;
29
+ locale: Locale;
30
+ }
31
+ export interface DocSection {
32
+ id: string;
33
+ title: string;
34
+ body: string;
35
+ }
36
+ export interface ComponentRecord {
37
+ id: string;
38
+ name: string;
39
+ exportName: string;
40
+ category: string;
41
+ description: string;
42
+ descriptionEn: string;
43
+ import: string;
44
+ props: ApiProp[];
45
+ events: ApiEvent[];
46
+ slots: ApiSlot[];
47
+ methods: ApiMethod[];
48
+ examples: ExampleItem[];
49
+ locales: Partial<Record<Locale, {
50
+ title: string;
51
+ description: string;
52
+ sections: DocSection[];
53
+ markdown: string;
54
+ }>>;
55
+ }
56
+ export interface GuideRecord {
57
+ id: string;
58
+ title: string;
59
+ titleEn: string;
60
+ description: string;
61
+ descriptionEn: string;
62
+ order: number;
63
+ locales: Partial<Record<Locale, {
64
+ title: string;
65
+ description: string;
66
+ markdown: string;
67
+ sections: DocSection[];
68
+ }>>;
69
+ }
70
+ export interface Catalog {
71
+ generatedAt: string;
72
+ library: {
73
+ name: string;
74
+ version: string;
75
+ };
76
+ mcp: {
77
+ name: string;
78
+ version: string;
79
+ };
80
+ components: ComponentRecord[];
81
+ guides: GuideRecord[];
82
+ }
83
+ /** Common product-language names mapped to the library's canonical component ids. */
84
+ export declare const componentAliases: Record<string, string>;
85
+ export declare function loadCatalog(): Catalog;
86
+ export declare function resolveLocale(mode?: string): Locale;
87
+ export declare function normalizeName(input: string): string;
88
+ export declare function findComponent(catalog: Catalog, name: string): ComponentRecord | undefined;
89
+ export declare function findGuide(catalog: Catalog, name: string): GuideRecord | undefined;
90
+ export declare function toKebab(name: string): string;
91
+ export declare function textResult(payload: unknown): {
92
+ structuredContent?: Record<string, unknown> | undefined;
93
+ content: {
94
+ type: "text";
95
+ text: string;
96
+ }[];
97
+ };
@@ -0,0 +1,75 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ /** Common product-language names mapped to the library's canonical component ids. */
5
+ export const componentAliases = {
6
+ datatable: 'Table',
7
+ 'data-table': 'Table',
8
+ 'data table': 'Table',
9
+ '数据表': 'Table',
10
+ '数据表格': 'Table',
11
+ '数据列表': 'Table',
12
+ tableview: 'Table',
13
+ formitem: 'Form',
14
+ 'form-item': 'Form',
15
+ '表单项': 'Form',
16
+ confirm: 'ConfirmDialog',
17
+ 'confirm-dialog': 'ConfirmDialog',
18
+ '确认框': 'ConfirmDialog',
19
+ '确认弹窗': 'ConfirmDialog',
20
+ pagination: 'Pagination',
21
+ pager: 'Pagination',
22
+ 分页器: 'Pagination',
23
+ layoutheader: 'Layout',
24
+ layoutsider: 'Layout',
25
+ layoutcontent: 'Layout',
26
+ layoutfooter: 'Layout',
27
+ griditem: 'Grid',
28
+ };
29
+ const here = dirname(fileURLToPath(import.meta.url));
30
+ export function loadCatalog() {
31
+ const path = join(here, '../data/catalog.json');
32
+ return JSON.parse(readFileSync(path, 'utf8'));
33
+ }
34
+ export function resolveLocale(mode) {
35
+ const value = (mode || '').toLowerCase();
36
+ if (value === 'en' || value === 'en-us' || value === 'english')
37
+ return 'en-US';
38
+ return 'zh-CN';
39
+ }
40
+ export function normalizeName(input) {
41
+ return input.trim().toLowerCase().replace(/^wk/, '').replace(/[-_\s]/g, '');
42
+ }
43
+ export function findComponent(catalog, name) {
44
+ const rawKey = name.trim().toLowerCase();
45
+ const alias = componentAliases[rawKey] || componentAliases[normalizeName(name)];
46
+ const key = normalizeName(alias || name);
47
+ return catalog.components.find((item) => {
48
+ return (normalizeName(item.id) === key ||
49
+ normalizeName(item.name) === key ||
50
+ normalizeName(item.exportName) === key);
51
+ });
52
+ }
53
+ export function findGuide(catalog, name) {
54
+ const key = normalizeName(name);
55
+ return catalog.guides.find((item) => {
56
+ return (normalizeName(item.id) === key ||
57
+ normalizeName(item.title) === key ||
58
+ normalizeName(item.titleEn || '') === key);
59
+ });
60
+ }
61
+ export function toKebab(name) {
62
+ return name
63
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
64
+ .replace(/_/g, '-')
65
+ .toLowerCase();
66
+ }
67
+ export function textResult(payload) {
68
+ const text = typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2);
69
+ return {
70
+ content: [{ type: 'text', text }],
71
+ ...(payload !== null && typeof payload === 'object'
72
+ ? { structuredContent: payload }
73
+ : {}),
74
+ };
75
+ }
@@ -0,0 +1,19 @@
1
+ export interface ComponentDecisionOption {
2
+ component: string;
3
+ when: string[];
4
+ whenEn: string[];
5
+ avoidWhen: string[];
6
+ avoidWhenEn: string[];
7
+ }
8
+ export interface ComponentDecision {
9
+ id: string;
10
+ title: string;
11
+ titleEn: string;
12
+ question: string;
13
+ questionEn: string;
14
+ keywords: string[];
15
+ options: ComponentDecisionOption[];
16
+ }
17
+ export declare const componentDecisions: ComponentDecision[];
18
+ export declare function findDecision(name: string): ComponentDecision | undefined;
19
+ export declare function scoreDecision(decision: ComponentDecision, query: string): number;
@@ -0,0 +1,155 @@
1
+ export const componentDecisions = [
2
+ {
3
+ id: 'overlay-choice',
4
+ title: '如何选择浮层组件',
5
+ titleEn: 'Choosing an overlay component',
6
+ question: '这是确认、短任务、上下文操作,还是需要保留页面上下文的编辑?',
7
+ questionEn: 'Is this a confirmation, short task, contextual action, or an edit that needs page context?',
8
+ keywords: ['弹窗', '浮层', '模态', '侧栏', '确认', '编辑', 'dialog', 'drawer', 'popover', 'tooltip', 'modal', 'side editing', 'side panel', 'context'],
9
+ options: [
10
+ {
11
+ component: 'Dialog',
12
+ when: ['需要用户聚焦完成一项短到中等任务', '需要确认危险操作', '内容不适合直接放在页面流中'],
13
+ whenEn: ['The user should focus on a short or medium-sized task', 'A destructive action needs confirmation', 'Content should not interrupt the page flow'],
14
+ avoidWhen: ['内容接近完整页面', '用户需要持续查看底层页面上下文'],
15
+ avoidWhenEn: ['The content is effectively a full page', 'The user must continuously reference the underlying page'],
16
+ },
17
+ {
18
+ component: 'Drawer',
19
+ when: ['需要侧边编辑或查看详情', '需要保留底层列表或工作区上下文', '内容比普通确认框更长'],
20
+ whenEn: ['Side editing or detail inspection is needed', 'The underlying list or workspace context should remain visible', 'The content is longer than a normal confirmation'],
21
+ avoidWhen: ['只是简单确认', '操作必须阻塞用户直到明确确认'],
22
+ avoidWhenEn: ['It is only a simple confirmation', 'The action must block the user until an explicit decision'],
23
+ },
24
+ {
25
+ component: 'Popover',
26
+ when: ['轻量上下文操作或补充信息', '不需要阻塞页面', '内容与触发元素强相关'],
27
+ whenEn: ['Lightweight contextual actions or supporting information', 'The page should not be blocked', 'The content is tightly related to the trigger'],
28
+ avoidWhen: ['需要复杂表单或危险操作确认'],
29
+ avoidWhenEn: ['A complex form or destructive confirmation is required'],
30
+ },
31
+ {
32
+ component: 'Tooltip',
33
+ when: ['只展示简短说明', '用户悬停或聚焦控件时需要补充提示'],
34
+ whenEn: ['Only a short explanation is needed', 'Extra help is needed on hover or focus'],
35
+ avoidWhen: ['信息是必需内容', '需要放置交互控件'],
36
+ avoidWhenEn: ['The information is essential content', 'Interactive controls need to be placed inside'],
37
+ },
38
+ ],
39
+ },
40
+ {
41
+ id: 'data-display-choice',
42
+ title: '如何选择数据展示组件',
43
+ titleEn: 'Choosing a data display component',
44
+ question: '用户需要比较行列数据、浏览卡片,还是查看树状层级?',
45
+ questionEn: 'Does the user need to compare rows and columns, browse cards, or inspect a hierarchy?',
46
+ keywords: ['表格', '列表', '卡片', '树', '层级', '数据展示', 'table', 'list', 'tree', 'dataview'],
47
+ options: [
48
+ {
49
+ component: 'Table',
50
+ when: ['数据有稳定列结构', '用户需要排序、筛选、批量或行操作', '需要高密度比较多条记录'],
51
+ whenEn: ['Data has a stable column structure', 'Users need sorting, filtering, bulk actions, or row actions', 'Many records must be compared at high density'],
52
+ avoidWhen: ['每条数据结构差异很大', '移动端无法承载横向列结构'],
53
+ avoidWhenEn: ['Each record has a very different structure', 'A horizontal column layout cannot work on mobile'],
54
+ },
55
+ {
56
+ component: 'DataView',
57
+ when: ['数据适合卡片或自定义列表项', '视觉浏览比列对齐更重要', '同一数据需要切换多种展示布局'],
58
+ whenEn: ['Data fits cards or custom list items', 'Visual browsing matters more than column alignment', 'The same data needs multiple presentation layouts'],
59
+ avoidWhen: ['用户必须精确比较字段', '需要复杂列级排序或固定列'],
60
+ avoidWhenEn: ['Users must compare fields precisely', 'Complex column sorting or frozen columns are required'],
61
+ },
62
+ {
63
+ component: 'TreeTable',
64
+ when: ['数据同时具有表格列和父子层级', '用户需要展开、收起层级节点'],
65
+ whenEn: ['Data has both table columns and parent-child hierarchy', 'Users need to expand and collapse hierarchy nodes'],
66
+ avoidWhen: ['数据没有真实层级关系', '普通 Table 已能表达关系'],
67
+ avoidWhenEn: ['There is no real hierarchy', 'A regular Table already expresses the relationship'],
68
+ },
69
+ {
70
+ component: 'Tree',
71
+ when: ['主要任务是浏览或选择层级节点', '节点信息不需要多列比较'],
72
+ whenEn: ['The main task is browsing or selecting hierarchy nodes', 'Nodes do not need multi-column comparison'],
73
+ avoidWhen: ['每行需要展示多个可比较字段'],
74
+ avoidWhenEn: ['Each row needs several comparable fields'],
75
+ },
76
+ ],
77
+ },
78
+ {
79
+ id: 'selection-choice',
80
+ title: '如何选择选择器',
81
+ titleEn: 'Choosing a selection control',
82
+ question: '选项是平面少量、层级结构、多选标签,还是需要输入搜索?',
83
+ questionEn: 'Are options a small flat set, a hierarchy, multi-select tags, or searchable input?',
84
+ keywords: ['选择', '下拉', '多选', '树选择', '搜索选择', 'select', 'treeselect', 'autocomplete', 'dropdown'],
85
+ options: [
86
+ {
87
+ component: 'Select',
88
+ when: ['平面选项数量中等', '需要单选或多选', '表单字段需要明确选项集合'],
89
+ whenEn: ['The options are a medium-sized flat set', 'Single or multiple selection is needed', 'The form needs a defined option set'],
90
+ avoidWhen: ['选项有明显层级', '用户需要输入自由文本并搜索建议'],
91
+ avoidWhenEn: ['Options have a meaningful hierarchy', 'Users need free text with suggestions'],
92
+ },
93
+ {
94
+ component: 'TreeSelect',
95
+ when: ['选项有父子层级', '用户需要按组织、分类或资源树选择'],
96
+ whenEn: ['Options have parent-child hierarchy', 'Users select from organizations, categories, or resource trees'],
97
+ avoidWhen: ['选项只是简单平面枚举'],
98
+ avoidWhenEn: ['Options are a simple flat enum'],
99
+ },
100
+ {
101
+ component: 'AutoComplete',
102
+ when: ['用户需要输入关键词搜索建议', '候选项很多或来自远程接口', '输入值本身也有意义'],
103
+ whenEn: ['Users type keywords to search suggestions', 'There are many or remote candidates', 'The entered value is meaningful itself'],
104
+ avoidWhen: ['用户只能从固定枚举中选择', '不应该允许自由输入'],
105
+ avoidWhenEn: ['Users must choose from a fixed enum', 'Free input must not be allowed'],
106
+ },
107
+ ],
108
+ },
109
+ {
110
+ id: 'surface-choice',
111
+ title: '如何选择内容容器',
112
+ titleEn: 'Choosing a content surface',
113
+ question: '内容是否需要独立的视觉表面、标题和边界?',
114
+ questionEn: 'Does the content need an independent visual surface, heading, and boundary?',
115
+ keywords: ['容器', '卡片', '面板', '分组', '表面', 'card', 'panel', 'fieldset', 'surface'],
116
+ options: [
117
+ {
118
+ component: 'Card',
119
+ when: ['内容是页面中的独立业务区块', '需要标题、副标题、页脚或 hover 表面', '需要清晰的边界和内边距'],
120
+ whenEn: ['The content is an independent business section', 'A title, subtitle, footer, or hover surface is useful', 'A clear boundary and padding are needed'],
121
+ avoidWhen: ['页面已有过多嵌套表面', '内容只是简单分组'],
122
+ avoidWhenEn: ['The page already has too many nested surfaces', 'The content is only a simple group'],
123
+ },
124
+ {
125
+ component: 'Panel',
126
+ when: ['需要可折叠或强调一个较长内容区块', '内容具有明确的面板标题'],
127
+ whenEn: ['A collapsible or prominent longer section is needed', 'The content has a clear panel heading'],
128
+ avoidWhen: ['只需要普通内容容器', '标题和边界会增加视觉噪音'],
129
+ avoidWhenEn: ['A regular content container is enough', 'A heading and boundary would add visual noise'],
130
+ },
131
+ {
132
+ component: 'Fieldset',
133
+ when: ['需要语义化分组相关表单字段', '分组标题对理解表单很重要'],
134
+ whenEn: ['Related form fields need semantic grouping', 'A group title is important for understanding the form'],
135
+ avoidWhen: ['内容不是表单字段', '只是为了增加装饰边框'],
136
+ avoidWhenEn: ['The content is not form fields', 'The border would be purely decorative'],
137
+ },
138
+ ],
139
+ },
140
+ ];
141
+ export function findDecision(name) {
142
+ const key = name.trim().toLowerCase().replace(/[-_\s]/g, '');
143
+ return componentDecisions.find((decision) => decision.id.replace(/[-_\s]/g, '') === key);
144
+ }
145
+ export function scoreDecision(decision, query) {
146
+ const normalized = query.toLowerCase().trim();
147
+ return decision.keywords.reduce((score, keyword) => {
148
+ const key = keyword.toLowerCase();
149
+ if (normalized === key)
150
+ return score + 100;
151
+ if (normalized.includes(key) || key.includes(normalized))
152
+ return score + 20;
153
+ return score;
154
+ }, 0);
155
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,106 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { z } from 'zod';
4
+ import { registerCatalogResources } from './resources.js';
5
+ import { createToolHandlers } from './tools.js';
6
+ const handlers = createToolHandlers();
7
+ const server = new McpServer({
8
+ name: handlers.catalog.mcp.name,
9
+ version: handlers.catalog.mcp.version,
10
+ });
11
+ registerCatalogResources(server, handlers.catalog);
12
+ function register(name, description, inputSchema, handler) {
13
+ return server.registerTool(name, {
14
+ title: description.split('.')[0],
15
+ description,
16
+ inputSchema,
17
+ annotations: {
18
+ readOnlyHint: true,
19
+ destructiveHint: false,
20
+ idempotentHint: true,
21
+ openWorldHint: false,
22
+ },
23
+ }, handler);
24
+ }
25
+ register('list', 'List morya-ui components, guides, examples, categories, or page patterns.', {
26
+ kind: z
27
+ .enum(['components', 'guides', 'examples', 'categories', 'patterns'])
28
+ .optional()
29
+ .describe('What to list. Defaults to components.'),
30
+ mode: z.string().optional().describe('Locale mode: zh / en. Defaults to zh.'),
31
+ limit: z.number().int().min(1).max(200).optional(),
32
+ offset: z.number().int().min(0).optional(),
33
+ }, async (args) => handlers.list(args));
34
+ register('search', 'Search components, guides, API text, examples, page patterns, and component decision guides.', {
35
+ query: z.string().min(1),
36
+ scope: z.enum(['all', 'components', 'guides', 'api', 'examples', 'patterns', 'decisions']).optional(),
37
+ mode: z.string().optional(),
38
+ limit: z.number().int().min(1).max(50).optional(),
39
+ offset: z.number().int().min(0).optional(),
40
+ }, async (args) => handlers.search(args));
41
+ register('get_component', 'Read component docs and metadata from the generated catalog.', {
42
+ component: z.string().min(1).optional(),
43
+ components: z.array(z.string()).max(10).optional(),
44
+ mode: z.string().optional(),
45
+ detail: z.enum(['compact', 'full']).optional(),
46
+ includeApi: z.boolean().optional(),
47
+ includeExamples: z.boolean().optional(),
48
+ examplesLimit: z.number().int().min(1).max(100).optional(),
49
+ examplesOffset: z.number().int().min(0).optional(),
50
+ sections: z.array(z.string()).optional(),
51
+ }, async (args) => handlers.getComponent(args));
52
+ register('get_example', 'Return one source-backed example for a component.', {
53
+ component: z.string().min(1),
54
+ mode: z.string().optional(),
55
+ section: z.string().optional(),
56
+ variant: z.string().optional(),
57
+ }, async (args) => handlers.getExample(args));
58
+ register('get_guide', 'Read a guide (introduction, quick-start, theme, config, …).', {
59
+ guide: z.string().min(1),
60
+ mode: z.string().optional(),
61
+ section: z.string().optional(),
62
+ detail: z.enum(['compact', 'full']).optional(),
63
+ }, async (args) => handlers.getGuide(args));
64
+ register('get_setup', 'Return installation and setup guidance for consuming morya-ui.', {
65
+ environment: z.string().optional(),
66
+ mode: z.string().optional(),
67
+ }, async (args) => handlers.getSetup(args));
68
+ register('validate_usage', 'Validate component usage snippets against documented props/events.', {
69
+ component: z.string().min(1).optional(),
70
+ code: z.string().min(1).optional(),
71
+ mode: z.string().optional(),
72
+ usages: z
73
+ .array(z.object({
74
+ component: z.string().optional(),
75
+ code: z.string().optional(),
76
+ }))
77
+ .max(10)
78
+ .optional(),
79
+ }, async (args) => handlers.validateUsage(args));
80
+ register('list_patterns', 'List reusable page patterns for composing morya-ui components.', {
81
+ mode: z.string().optional(),
82
+ limit: z.number().int().min(1).max(200).optional(),
83
+ offset: z.number().int().min(0).optional(),
84
+ }, async (args) => handlers.listPatterns(args));
85
+ register('get_pattern', 'Read the structure, component composition, layout, and interaction rules for a page pattern.', {
86
+ pattern: z.string().min(1),
87
+ mode: z.string().optional(),
88
+ }, async (args) => handlers.getPattern(args));
89
+ register('recommend_page', 'Recommend a page pattern and component composition from product intent. Pass includeScaffold: true for starter Vue code.', {
90
+ intent: z.string().min(1),
91
+ pageType: z.string().optional(),
92
+ features: z.array(z.string()).max(20).optional(),
93
+ mode: z.string().optional(),
94
+ includeScaffold: z.boolean().optional(),
95
+ }, async (args) => handlers.recommendPage(args));
96
+ register('get_design_rules', 'Return design-token, semantic-action, accessibility, and composition rules for generated pages.', { mode: z.string().optional() }, async (args) => handlers.getDesignRules(args));
97
+ register('recommend_component', 'List, read, or recommend component selection guides. Omit query/decision to list; pass decision only to read; pass query to recommend.', {
98
+ query: z.string().optional(),
99
+ decision: z.string().optional(),
100
+ mode: z.string().optional(),
101
+ limit: z.number().int().min(1).max(200).optional(),
102
+ offset: z.number().int().min(0).optional(),
103
+ }, async (args) => handlers.recommendComponent(args));
104
+ register('version', 'Return MCP package, library version, and catalog status.', {}, async () => handlers.version());
105
+ const transport = new StdioServerTransport();
106
+ await server.connect(transport);
@@ -0,0 +1,80 @@
1
+ export interface PatternComponent {
2
+ component: string;
3
+ role: string;
4
+ reason?: string;
5
+ required?: boolean;
6
+ }
7
+ export interface PagePattern {
8
+ id: string;
9
+ title: string;
10
+ titleEn: string;
11
+ description: string;
12
+ descriptionEn: string;
13
+ keywords: string[];
14
+ goldenPage?: string;
15
+ components: PatternComponent[];
16
+ structure: string[];
17
+ layout: Record<string, string>;
18
+ styleRules: string[];
19
+ interactionRules: string[];
20
+ avoid: string[];
21
+ }
22
+ export declare const pagePatterns: PagePattern[];
23
+ export declare const designRules: {
24
+ readonly tokens: {
25
+ readonly colors: readonly ["--m-color-primary", "--m-color-surface", "--m-color-text", "--m-color-border"];
26
+ readonly spacing: "--m-space-*";
27
+ readonly radius: "--m-radius-sm/md/lg";
28
+ readonly typography: "--m-font-size-xs/sm/md/lg";
29
+ readonly motion: "--m-motion-fast/normal";
30
+ };
31
+ readonly actions: {
32
+ readonly primary: {
33
+ readonly component: "MButton";
34
+ readonly props: readonly ["severity omitted or primary"];
35
+ };
36
+ readonly secondary: {
37
+ readonly component: "MButton";
38
+ readonly props: readonly ["severity=\"secondary\"", "outlined or text"];
39
+ };
40
+ readonly destructive: {
41
+ readonly component: "MButton";
42
+ readonly props: readonly ["severity=\"danger\""];
43
+ readonly requiresConfirmation: true;
44
+ };
45
+ readonly cancel: {
46
+ readonly component: "MButton";
47
+ readonly props: readonly ["severity=\"secondary\"", "text"];
48
+ };
49
+ };
50
+ readonly status: {
51
+ readonly component: "MTag";
52
+ readonly mapping: {
53
+ readonly active: "success";
54
+ readonly pending: "warn";
55
+ readonly disabled: "secondary";
56
+ readonly error: "danger";
57
+ };
58
+ };
59
+ readonly feedback: {
60
+ readonly default: "message";
61
+ readonly message: {
62
+ readonly api: "message.success | info | warn | error";
63
+ readonly when: readonly ["单行操作回执(已保存/已删除/已创建)", "轻量警告或错误", "复制成功等一句话反馈"];
64
+ readonly avoid: readonly ["不要写 summary/detail 结构"];
65
+ };
66
+ readonly toast: {
67
+ readonly api: "toast.success | info | warn | error({ summary, detail? })";
68
+ readonly when: readonly ["同时需要标题与补充说明", "后台任务/批量结果含统计", "异步通知感、角落堆叠"];
69
+ readonly avoid: readonly ["仅有单行文案时不要使用 Toast"];
70
+ };
71
+ readonly inlineMessage: {
72
+ readonly component: "MMessage";
73
+ readonly when: readonly ["登录/表单区常驻错误", "页面级错误需与表单同区展示"];
74
+ };
75
+ readonly doc: "docs/feedback-message-vs-toast.md";
76
+ };
77
+ readonly global: readonly ["优先使用组件库组件和 --m-* Token,不重复维护第二套色板", "操作反馈默认 message;无 detail 禁止用 toast.add({ summary only })", "图标按钮必须提供 aria-label 或 ariaLabel", "表单字段必须有可见 label 或等价的可访问名称", "浮层默认 Teleport 到 body;只有有明确布局约束时才改 appendTo", "优先使用组件的 documented variant,不通过深层 CSS 覆盖内部样式"];
78
+ };
79
+ export declare function findPattern(input: string): PagePattern | undefined;
80
+ export declare function scorePattern(pattern: PagePattern, query: string): number;