feat(mcp): add resources and prompts
Resources: pascal://scene/current (JSON), /scene/current/summary
(markdown with per-level counts, floor areas, bbox), /catalog/items
(returns catalog_unavailable in headless mode), and the templated
pascal://constraints/{levelId} which exposes slabs + wall footprints
via @pascal-app/core/wall helpers.
Prompts: from_brief (generate scene from a natural-language brief),
iterate_on_feedback (minimal-diff patch proposals), and
renovation_from_photos (orchestrates the vision tools).
17 tests, all passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
58ad89e80b
commit
570c605446
@@ -0,0 +1,61 @@
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||
import { z } from 'zod'
|
||||
import type { SceneBridge } from '../bridge/scene-bridge'
|
||||
|
||||
const PREAMBLE = [
|
||||
'You are a Pascal 3D scene designer.',
|
||||
'You have access to the `apply_patch` tool for all scene mutations. Prefer it over individual create_* tools so that your changes land as a single undoable step.',
|
||||
'Build incrementally. Starting from an empty scene, first create a Site, then a Building, then one or more Levels; only after that do you create walls, zones, slabs, items, and openings.',
|
||||
'Respect these invariants:',
|
||||
' - Levels live under a Building.',
|
||||
' - Walls, fences, zones, slabs, ceilings, roofs, stairs live under a Level.',
|
||||
' - Doors and windows live under a Wall (parentId = wallId).',
|
||||
' - Items live under a Wall, Ceiling, or Site.',
|
||||
'Use realistic dimensions in meters. Keep wall thickness small (0.1–0.3 m) and ceiling height 2.4–3.0 m unless the brief dictates otherwise.',
|
||||
'Respond ONLY with tool calls. Do not produce verbose narrative or prose; keep any explanations in short tool-call arguments.',
|
||||
].join('\n')
|
||||
|
||||
/**
|
||||
* Build the user-facing prompt text for `from_brief`. Pure function for testability.
|
||||
*/
|
||||
export function buildFromBriefPrompt(args: {
|
||||
brief: string
|
||||
constraints?: string | undefined
|
||||
}): string {
|
||||
const parts: string[] = [PREAMBLE, '', '## Brief', args.brief.trim()]
|
||||
if (args.constraints && args.constraints.trim().length > 0) {
|
||||
parts.push('', '## Constraints', args.constraints.trim())
|
||||
}
|
||||
parts.push(
|
||||
'',
|
||||
'## Task',
|
||||
'Produce a plan of `apply_patch` calls that realises the brief within the stated constraints. Start from an empty site. Call the vision / query tools only if you need extra context.',
|
||||
)
|
||||
return parts.join('\n')
|
||||
}
|
||||
|
||||
export function registerFromBrief(server: McpServer, _bridge: SceneBridge): void {
|
||||
server.registerPrompt(
|
||||
'from_brief',
|
||||
{
|
||||
title: 'Generate a Pascal scene from a brief',
|
||||
description:
|
||||
'Produces a plan of apply_patch calls to create a scene from a natural-language brief.',
|
||||
argsSchema: {
|
||||
brief: z.string(),
|
||||
constraints: z.string().optional(),
|
||||
},
|
||||
},
|
||||
async ({ brief, constraints }) => ({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: {
|
||||
type: 'text',
|
||||
text: buildFromBriefPrompt({ brief, constraints }),
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user