feat(mcp): add guided construction workflows

This commit is contained in:
Aymeric Rabot
2026-04-27 14:31:04 -04:00
parent b3d1f663f6
commit 3d5c87a651
48 changed files with 3877 additions and 115 deletions
+315
View File
@@ -0,0 +1,315 @@
import type { AssetInput } from '@pascal-app/core/schema'
/**
* Small built-in catalog for standalone/headless MCP use.
*
* The editor has a much larger UI catalog, but depending on `@pascal-app/editor`
* from the MCP package would pull browser/React code into the headless server.
* These entries mirror the stable IDs and asset paths used by the editor for
* common AI-generated residential layouts.
*/
export const MCP_CATALOG_ITEMS: AssetInput[] = [
{
id: 'double-bed',
category: 'furniture',
tags: ['floor', 'bedroom'],
name: 'Double Bed',
thumbnail: '/items/double-bed/thumbnail.webp',
src: '/items/double-bed/model.glb',
dimensions: [2, 0.8, 2.5],
offset: [0, 0, -0.03],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'single-bed',
category: 'furniture',
tags: ['floor', 'bedroom'],
name: 'Single Bed',
thumbnail: '/items/single-bed/thumbnail.webp',
src: '/items/single-bed/model.glb',
dimensions: [1.5, 0.7, 2.5],
offset: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'bedside-table',
category: 'furniture',
tags: ['floor', 'bedroom'],
name: 'Bedside Table',
thumbnail: '/items/bedside-table/thumbnail.webp',
src: '/items/bedside-table/model.glb',
dimensions: [0.5, 0.5, 0.5],
offset: [0, 0, -0.01],
rotation: [0, 0, 0],
scale: [1, 1, 1],
surface: { height: 0.5 },
},
{
id: 'dresser',
category: 'furniture',
tags: ['floor', 'storage', 'bedroom'],
name: 'Dresser',
thumbnail: '/items/dresser/thumbnail.webp',
src: '/items/dresser/model.glb',
dimensions: [1.5, 0.8, 1],
offset: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
surface: { height: 0.8 },
},
{
id: 'closet',
category: 'furniture',
tags: ['floor', 'storage', 'bedroom'],
name: 'Closet',
thumbnail: '/items/closet/thumbnail.webp',
src: '/items/closet/model.glb',
dimensions: [2, 2.5, 1],
offset: [0, 0, -0.01],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'sofa',
category: 'furniture',
tags: ['floor', 'seating', 'living'],
name: 'Sofa',
thumbnail: '/items/sofa/thumbnail.webp',
src: '/items/sofa/model.glb',
dimensions: [2.5, 0.8, 1.5],
offset: [0, 0, 0.04],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'livingroom-chair',
category: 'furniture',
tags: ['floor', 'seating', 'living'],
name: 'Livingroom Chair',
thumbnail: '/items/livingroom-chair/thumbnail.webp',
src: '/items/livingroom-chair/model.glb',
dimensions: [1.5, 0.8, 1.5],
offset: [0.01, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'coffee-table',
category: 'furniture',
tags: ['floor', 'table', 'living'],
name: 'Coffee Table',
thumbnail: '/items/coffee-table/thumbnail.webp',
src: '/items/coffee-table/model.glb',
dimensions: [2, 0.4, 1.5],
offset: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
surface: { height: 0.3 },
},
{
id: 'tv-stand',
category: 'furniture',
tags: ['floor', 'storage', 'living'],
name: 'TV Stand',
thumbnail: '/items/tv-stand/thumbnail.webp',
src: '/items/tv-stand/model.glb',
dimensions: [2, 0.4, 0.5],
offset: [0, 0.21, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
surface: { height: 0.36 },
},
{
id: 'shelf',
category: 'furniture',
tags: ['wall', 'storage'],
name: 'Shelf',
thumbnail: '/items/shelf/thumbnail.webp',
src: '/items/shelf/model.glb',
dimensions: [1, 0.5, 0.7],
offset: [0, 0.1, 0.01],
rotation: [0, 0, 0],
scale: [1, 1, 1],
attachTo: 'wall-side',
surface: { height: 0.12 },
},
{
id: 'dining-table',
category: 'furniture',
tags: ['floor', 'table', 'dining'],
name: 'Dining Table',
thumbnail: '/items/dining-table/thumbnail.webp',
src: '/items/dining-table/model.glb',
dimensions: [2.5, 0.8, 1],
offset: [0, 0, -0.01],
rotation: [0, 0, 0],
scale: [1, 1, 1],
surface: { height: 0.8 },
},
{
id: 'dining-chair',
category: 'furniture',
tags: ['floor', 'seating', 'dining'],
name: 'Dining Chair',
thumbnail: '/items/dining-chair/thumbnail.webp',
src: '/items/dining-chair/model.glb',
dimensions: [0.5, 1, 0.5],
offset: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'kitchen',
category: 'kitchen',
tags: ['floor', 'large', 'kitchen'],
name: 'Kitchen',
thumbnail: '/items/kitchen/thumbnail.webp',
src: '/items/kitchen/model.glb',
dimensions: [2.5, 1.1, 1],
offset: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'kitchen-counter',
category: 'kitchen',
tags: ['floor', 'large', 'storage', 'kitchen'],
name: 'Kitchen Counter',
thumbnail: '/items/kitchen-counter/thumbnail.webp',
src: '/items/kitchen-counter/model.glb',
dimensions: [2, 0.8, 1],
offset: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
surface: { height: 0.75 },
},
{
id: 'stove',
category: 'kitchen',
tags: ['floor', 'large', 'kitchen'],
name: 'Stove',
thumbnail: '/items/stove/thumbnail.webp',
src: '/items/stove/model.glb',
dimensions: [1, 1, 1],
offset: [0, 0, -0.05],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'fridge',
category: 'kitchen',
tags: ['floor', 'large', 'kitchen'],
name: 'Fridge',
thumbnail: '/items/fridge/thumbnail.webp',
src: '/items/fridge/model.glb',
dimensions: [1, 2, 1],
offset: [0.01, 0, -0.05],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'toilet',
category: 'bathroom',
tags: ['floor', 'large', 'bathroom'],
name: 'Toilet',
thumbnail: '/items/toilet/thumbnail.webp',
src: '/items/toilet/model.glb',
dimensions: [1, 0.9, 1],
offset: [0, 0, -0.23],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'bathroom-sink',
category: 'bathroom',
tags: ['floor', 'large', 'bathroom'],
name: 'Bathroom Sink',
thumbnail: '/items/bathroom-sink/thumbnail.webp',
src: '/items/bathroom-sink/model.glb',
dimensions: [2, 1, 1.5],
offset: [0.11, 0, 0.02],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'shower-square',
category: 'bathroom',
tags: ['floor', 'large', 'bathroom'],
name: 'Squared Shower',
thumbnail: '/items/shower-square/thumbnail.webp',
src: '/items/shower-square/model.glb',
dimensions: [1, 2, 1],
offset: [0.41, 0, -0.42],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'bathtub',
category: 'bathroom',
tags: ['floor', 'large', 'bathroom'],
name: 'Bathtub',
thumbnail: '/items/bathtub/thumbnail.webp',
src: '/items/bathtub/model.glb',
dimensions: [2.5, 0.8, 1.5],
offset: [0, 0, 0.01],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'washing-machine',
category: 'bathroom',
tags: ['floor', 'large', 'electronics', 'laundry'],
name: 'Washing Machine',
thumbnail: '/items/washing-machine/thumbnail.webp',
src: '/items/washing-machine/model.glb',
dimensions: [1, 1, 1],
offset: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'drying-rack',
category: 'bathroom',
tags: ['floor', 'laundry'],
name: 'Drying Rack',
thumbnail: '/items/drying-rack/thumbnail.webp',
src: '/items/drying-rack/model.glb',
dimensions: [2, 1.1, 1],
offset: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
{
id: 'coat-rack',
category: 'furniture',
tags: ['floor', 'storage', 'entry'],
name: 'Coat Rack',
thumbnail: '/items/coat-rack/thumbnail.webp',
src: '/items/coat-rack/model.glb',
dimensions: [0.5, 1.8, 0.5],
offset: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
]
export function findCatalogItem(id: string): AssetInput | undefined {
return MCP_CATALOG_ITEMS.find((item) => item.id === id)
}
export function searchCatalogItems(args: {
query: string
category?: string | undefined
}): AssetInput[] {
const terms = args.query.trim().toLowerCase().split(/\s+/).filter(Boolean)
return MCP_CATALOG_ITEMS.filter((item) => {
if (args.category && item.category !== args.category) return false
const haystack = [item.id, item.name, item.category, ...(item.tags ?? [])]
.join(' ')
.toLowerCase()
return terms.every((term) => haystack.includes(term))
})
}