@pascal-app/mcp 0.2.0
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/CHANGELOG.md +39 -0
- package/README.md +330 -0
- package/dist/bin/pascal-mcp.d.ts +3 -0
- package/dist/bin/pascal-mcp.d.ts.map +1 -0
- package/dist/bin/pascal-mcp.js +91 -0
- package/dist/bridge/index.d.ts +3 -0
- package/dist/bridge/index.d.ts.map +1 -0
- package/dist/bridge/index.js +1 -0
- package/dist/bridge/node-shims.d.ts +16 -0
- package/dist/bridge/node-shims.d.ts.map +1 -0
- package/dist/bridge/node-shims.js +25 -0
- package/dist/bridge/scene-bridge.d.ts +158 -0
- package/dist/bridge/scene-bridge.d.ts.map +1 -0
- package/dist/bridge/scene-bridge.js +482 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/lib/rehydrate-site-children.d.ts +12 -0
- package/dist/lib/rehydrate-site-children.d.ts.map +1 -0
- package/dist/lib/rehydrate-site-children.js +40 -0
- package/dist/lib/safe-fetch.d.ts +19 -0
- package/dist/lib/safe-fetch.d.ts.map +1 -0
- package/dist/lib/safe-fetch.js +221 -0
- package/dist/operations/index.d.ts +2 -0
- package/dist/operations/index.d.ts.map +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/scene-operations.d.ts +67 -0
- package/dist/operations/scene-operations.d.ts.map +1 -0
- package/dist/operations/scene-operations.js +192 -0
- package/dist/prompts/from-brief.d.ts +11 -0
- package/dist/prompts/from-brief.d.ts.map +1 -0
- package/dist/prompts/from-brief.js +48 -0
- package/dist/prompts/index.d.ts +10 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +14 -0
- package/dist/prompts/iterate-on-feedback.d.ts +11 -0
- package/dist/prompts/iterate-on-feedback.d.ts.map +1 -0
- package/dist/prompts/iterate-on-feedback.js +41 -0
- package/dist/prompts/renovation-from-photos.d.ts +24 -0
- package/dist/prompts/renovation-from-photos.d.ts.map +1 -0
- package/dist/prompts/renovation-from-photos.js +135 -0
- package/dist/prompts/scene-guidance.d.ts +2 -0
- package/dist/prompts/scene-guidance.d.ts.map +1 -0
- package/dist/prompts/scene-guidance.js +24 -0
- package/dist/resources/agent-guide.d.ts +5 -0
- package/dist/resources/agent-guide.d.ts.map +1 -0
- package/dist/resources/agent-guide.js +85 -0
- package/dist/resources/catalog-items.d.ts +10 -0
- package/dist/resources/catalog-items.d.ts.map +1 -0
- package/dist/resources/catalog-items.js +29 -0
- package/dist/resources/constraints.d.ts +9 -0
- package/dist/resources/constraints.d.ts.map +1 -0
- package/dist/resources/constraints.js +69 -0
- package/dist/resources/index.d.ts +15 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +23 -0
- package/dist/resources/scene-current.d.ts +9 -0
- package/dist/resources/scene-current.d.ts.map +1 -0
- package/dist/resources/scene-current.js +20 -0
- package/dist/resources/scene-summary.d.ts +10 -0
- package/dist/resources/scene-summary.d.ts.map +1 -0
- package/dist/resources/scene-summary.js +196 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +18 -0
- package/dist/storage/index.d.ts +13 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +14 -0
- package/dist/storage/slug.d.ts +21 -0
- package/dist/storage/slug.d.ts.map +1 -0
- package/dist/storage/slug.js +62 -0
- package/dist/storage/sqlite-driver.d.ts +18 -0
- package/dist/storage/sqlite-driver.d.ts.map +1 -0
- package/dist/storage/sqlite-driver.js +32 -0
- package/dist/storage/sqlite-scene-store.d.ts +51 -0
- package/dist/storage/sqlite-scene-store.d.ts.map +1 -0
- package/dist/storage/sqlite-scene-store.js +526 -0
- package/dist/storage/types.d.ts +136 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +28 -0
- package/dist/templates/empty-studio.d.ts +8 -0
- package/dist/templates/empty-studio.d.ts.map +1 -0
- package/dist/templates/empty-studio.js +230 -0
- package/dist/templates/garden-house.d.ts +8 -0
- package/dist/templates/garden-house.d.ts.map +1 -0
- package/dist/templates/garden-house.js +242 -0
- package/dist/templates/index.d.ts +23 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +20 -0
- package/dist/templates/two-bedroom.d.ts +8 -0
- package/dist/templates/two-bedroom.d.ts.map +1 -0
- package/dist/templates/two-bedroom.js +270 -0
- package/dist/tools/apply-patch.d.ts +25 -0
- package/dist/tools/apply-patch.d.ts.map +1 -0
- package/dist/tools/apply-patch.js +59 -0
- package/dist/tools/asset-catalog.d.ts +16 -0
- package/dist/tools/asset-catalog.d.ts.map +1 -0
- package/dist/tools/asset-catalog.js +308 -0
- package/dist/tools/check-collisions.d.ts +15 -0
- package/dist/tools/check-collisions.d.ts.map +1 -0
- package/dist/tools/check-collisions.js +61 -0
- package/dist/tools/construction-tools.d.ts +93 -0
- package/dist/tools/construction-tools.d.ts.map +1 -0
- package/dist/tools/construction-tools.js +369 -0
- package/dist/tools/create-level.d.ts +14 -0
- package/dist/tools/create-level.d.ts.map +1 -0
- package/dist/tools/create-level.js +48 -0
- package/dist/tools/create-wall.d.ts +15 -0
- package/dist/tools/create-wall.d.ts.map +1 -0
- package/dist/tools/create-wall.js +50 -0
- package/dist/tools/cut-opening.d.ts +18 -0
- package/dist/tools/cut-opening.d.ts.map +1 -0
- package/dist/tools/cut-opening.js +57 -0
- package/dist/tools/delete-node.d.ts +12 -0
- package/dist/tools/delete-node.d.ts.map +1 -0
- package/dist/tools/delete-node.js +37 -0
- package/dist/tools/describe-node.d.ts +17 -0
- package/dist/tools/describe-node.d.ts.map +1 -0
- package/dist/tools/describe-node.js +86 -0
- package/dist/tools/duplicate-level.d.ts +12 -0
- package/dist/tools/duplicate-level.d.ts.map +1 -0
- package/dist/tools/duplicate-level.js +56 -0
- package/dist/tools/errors.d.ts +20 -0
- package/dist/tools/errors.d.ts.map +1 -0
- package/dist/tools/errors.js +25 -0
- package/dist/tools/export-glb.d.ts +10 -0
- package/dist/tools/export-glb.d.ts.map +1 -0
- package/dist/tools/export-glb.js +24 -0
- package/dist/tools/export-json.d.ts +11 -0
- package/dist/tools/export-json.d.ts.map +1 -0
- package/dist/tools/export-json.js +23 -0
- package/dist/tools/find-nodes.d.ts +32 -0
- package/dist/tools/find-nodes.d.ts.map +1 -0
- package/dist/tools/find-nodes.js +105 -0
- package/dist/tools/geometry.d.ts +24 -0
- package/dist/tools/geometry.d.ts.map +1 -0
- package/dist/tools/geometry.js +94 -0
- package/dist/tools/get-node.d.ts +11 -0
- package/dist/tools/get-node.d.ts.map +1 -0
- package/dist/tools/get-node.js +27 -0
- package/dist/tools/get-scene.d.ts +11 -0
- package/dist/tools/get-scene.d.ts.map +1 -0
- package/dist/tools/get-scene.js +26 -0
- package/dist/tools/index.d.ts +12 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +64 -0
- package/dist/tools/live-sync.d.ts +11 -0
- package/dist/tools/live-sync.d.ts.map +1 -0
- package/dist/tools/live-sync.js +62 -0
- package/dist/tools/measure.d.ts +14 -0
- package/dist/tools/measure.d.ts.map +1 -0
- package/dist/tools/measure.js +137 -0
- package/dist/tools/photo-to-scene/index.d.ts +11 -0
- package/dist/tools/photo-to-scene/index.d.ts.map +1 -0
- package/dist/tools/photo-to-scene/index.js +11 -0
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts +26 -0
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -0
- package/dist/tools/photo-to-scene/photo-to-scene.js +323 -0
- package/dist/tools/place-item.d.ts +15 -0
- package/dist/tools/place-item.d.ts.map +1 -0
- package/dist/tools/place-item.js +84 -0
- package/dist/tools/redo.d.ts +11 -0
- package/dist/tools/redo.d.ts.map +1 -0
- package/dist/tools/redo.js +25 -0
- package/dist/tools/room-tools.d.ts +97 -0
- package/dist/tools/room-tools.d.ts.map +1 -0
- package/dist/tools/room-tools.js +510 -0
- package/dist/tools/scene-lifecycle/create-project.d.ts +33 -0
- package/dist/tools/scene-lifecycle/create-project.d.ts.map +1 -0
- package/dist/tools/scene-lifecycle/create-project.js +70 -0
- package/dist/tools/scene-lifecycle/delete-scene.d.ts +12 -0
- package/dist/tools/scene-lifecycle/delete-scene.d.ts.map +1 -0
- package/dist/tools/scene-lifecycle/delete-scene.js +42 -0
- package/dist/tools/scene-lifecycle/get-project-status.d.ts +31 -0
- package/dist/tools/scene-lifecycle/get-project-status.d.ts.map +1 -0
- package/dist/tools/scene-lifecycle/get-project-status.js +69 -0
- package/dist/tools/scene-lifecycle/index.d.ts +17 -0
- package/dist/tools/scene-lifecycle/index.d.ts.map +1 -0
- package/dist/tools/scene-lifecycle/index.js +29 -0
- package/dist/tools/scene-lifecycle/list-scenes.d.ts +27 -0
- package/dist/tools/scene-lifecycle/list-scenes.d.ts.map +1 -0
- package/dist/tools/scene-lifecycle/list-scenes.js +49 -0
- package/dist/tools/scene-lifecycle/load-scene.d.ts +31 -0
- package/dist/tools/scene-lifecycle/load-scene.d.ts.map +1 -0
- package/dist/tools/scene-lifecycle/load-scene.js +55 -0
- package/dist/tools/scene-lifecycle/metadata.d.ts +49 -0
- package/dist/tools/scene-lifecycle/metadata.d.ts.map +1 -0
- package/dist/tools/scene-lifecycle/metadata.js +68 -0
- package/dist/tools/scene-lifecycle/rename-scene.d.ts +22 -0
- package/dist/tools/scene-lifecycle/rename-scene.d.ts.map +1 -0
- package/dist/tools/scene-lifecycle/rename-scene.js +63 -0
- package/dist/tools/scene-lifecycle/save-scene.d.ts +42 -0
- package/dist/tools/scene-lifecycle/save-scene.d.ts.map +1 -0
- package/dist/tools/scene-lifecycle/save-scene.js +130 -0
- package/dist/tools/scene-lifecycle/test-utils.d.ts +39 -0
- package/dist/tools/scene-lifecycle/test-utils.d.ts.map +1 -0
- package/dist/tools/scene-lifecycle/test-utils.js +211 -0
- package/dist/tools/scene-query.d.ts +57 -0
- package/dist/tools/scene-query.d.ts.map +1 -0
- package/dist/tools/scene-query.js +645 -0
- package/dist/tools/schemas.d.ts +52 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +42 -0
- package/dist/tools/set-zone.d.ts +14 -0
- package/dist/tools/set-zone.d.ts.map +1 -0
- package/dist/tools/set-zone.js +48 -0
- package/dist/tools/templates/create-from-template.d.ts +54 -0
- package/dist/tools/templates/create-from-template.d.ts.map +1 -0
- package/dist/tools/templates/create-from-template.js +145 -0
- package/dist/tools/templates/create-house-from-brief.d.ts +35 -0
- package/dist/tools/templates/create-house-from-brief.d.ts.map +1 -0
- package/dist/tools/templates/create-house-from-brief.js +170 -0
- package/dist/tools/templates/index.d.ts +14 -0
- package/dist/tools/templates/index.d.ts.map +1 -0
- package/dist/tools/templates/index.js +18 -0
- package/dist/tools/templates/list-templates.d.ts +18 -0
- package/dist/tools/templates/list-templates.d.ts.map +1 -0
- package/dist/tools/templates/list-templates.js +36 -0
- package/dist/tools/undo.d.ts +11 -0
- package/dist/tools/undo.d.ts.map +1 -0
- package/dist/tools/undo.js +25 -0
- package/dist/tools/validate-scene.d.ts +14 -0
- package/dist/tools/validate-scene.d.ts.map +1 -0
- package/dist/tools/validate-scene.js +24 -0
- package/dist/tools/variants/generate-variants.d.ts +30 -0
- package/dist/tools/variants/generate-variants.d.ts.map +1 -0
- package/dist/tools/variants/generate-variants.js +160 -0
- package/dist/tools/variants/index.d.ts +9 -0
- package/dist/tools/variants/index.d.ts.map +1 -0
- package/dist/tools/variants/index.js +9 -0
- package/dist/tools/variants/mutations.d.ts +18 -0
- package/dist/tools/variants/mutations.d.ts.map +1 -0
- package/dist/tools/variants/mutations.js +324 -0
- package/dist/tools/vision/analyze-floorplan-image.d.ts +33 -0
- package/dist/tools/vision/analyze-floorplan-image.d.ts.map +1 -0
- package/dist/tools/vision/analyze-floorplan-image.js +146 -0
- package/dist/tools/vision/analyze-room-photo.d.ts +29 -0
- package/dist/tools/vision/analyze-room-photo.d.ts.map +1 -0
- package/dist/tools/vision/analyze-room-photo.js +130 -0
- package/dist/tools/vision/index.d.ts +12 -0
- package/dist/tools/vision/index.d.ts.map +1 -0
- package/dist/tools/vision/index.js +14 -0
- package/dist/transports/http.d.ts +36 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +203 -0
- package/dist/transports/stdio.d.ts +14 -0
- package/dist/transports/stdio.d.ts.map +1 -0
- package/dist/transports/stdio.js +16 -0
- package/package.json +86 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { AnyNode } from '@pascal-app/core/schema';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { SceneVersionConflictError } from '../../storage/types';
|
|
4
|
+
import { ErrorCode, throwMcpError } from '../errors';
|
|
5
|
+
import { appendLiveSceneEvent } from '../live-sync';
|
|
6
|
+
import { currentLevelContext, sceneMetaPayload } from './metadata';
|
|
7
|
+
export const saveSceneInput = {
|
|
8
|
+
id: z.string().min(1).max(64).optional(),
|
|
9
|
+
name: z.string().min(1).max(200),
|
|
10
|
+
projectId: z.string().optional(),
|
|
11
|
+
expectedVersion: z.number().int().positive().optional(),
|
|
12
|
+
saveMode: z
|
|
13
|
+
.enum(['draft', 'checkpoint'])
|
|
14
|
+
.default('draft')
|
|
15
|
+
.describe('`draft` updates the browser-visible working model without polluting version history. `checkpoint` creates a meaningful saved version.'),
|
|
16
|
+
publish: z
|
|
17
|
+
.boolean()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('For checkpoint saves, publish the checkpoint as the browser-visible version.'),
|
|
20
|
+
thumbnail: z.string().url().optional(),
|
|
21
|
+
includeCurrentScene: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.default(true)
|
|
24
|
+
.describe('If true, save the bridge current scene. If false, use the graph arg.'),
|
|
25
|
+
graph: z
|
|
26
|
+
.record(z.string(), z.unknown())
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Full SceneGraph { nodes, rootNodeIds, collections? } to save instead of the bridge state.'),
|
|
29
|
+
};
|
|
30
|
+
export const saveSceneOutput = {
|
|
31
|
+
id: z.string(),
|
|
32
|
+
name: z.string(),
|
|
33
|
+
projectId: z.string().nullable(),
|
|
34
|
+
thumbnailUrl: z.string().nullable(),
|
|
35
|
+
version: z.number(),
|
|
36
|
+
createdAt: z.string(),
|
|
37
|
+
updatedAt: z.string(),
|
|
38
|
+
ownerId: z.string().nullable(),
|
|
39
|
+
sizeBytes: z.number(),
|
|
40
|
+
nodeCount: z.number(),
|
|
41
|
+
url: z.string(),
|
|
42
|
+
editorUrl: z.string(),
|
|
43
|
+
published: z.boolean(),
|
|
44
|
+
isDraft: z.boolean(),
|
|
45
|
+
saveMode: z.enum(['draft', 'checkpoint']),
|
|
46
|
+
graphHash: z.string().optional(),
|
|
47
|
+
levelIds: z.array(z.string()),
|
|
48
|
+
defaultLevelId: z.string().nullable(),
|
|
49
|
+
};
|
|
50
|
+
export function registerSaveScene(server, bridge) {
|
|
51
|
+
server.registerTool('save_scene', {
|
|
52
|
+
title: 'Save scene',
|
|
53
|
+
description: 'Save the current scene (or a provided graph) to the SceneStore. Defaults to a browser-visible draft save so agents can iterate without creating many project versions. Use saveMode: "checkpoint" for meaningful version history.',
|
|
54
|
+
inputSchema: saveSceneInput,
|
|
55
|
+
outputSchema: saveSceneOutput,
|
|
56
|
+
}, async ({ id, name, projectId, expectedVersion, saveMode, publish, thumbnail, includeCurrentScene, graph, }) => {
|
|
57
|
+
let sceneGraph;
|
|
58
|
+
if (includeCurrentScene) {
|
|
59
|
+
const validation = bridge.validateScene();
|
|
60
|
+
if (!validation.valid) {
|
|
61
|
+
throwMcpError(ErrorCode.InvalidRequest, 'scene_invalid', { errors: validation.errors });
|
|
62
|
+
}
|
|
63
|
+
sceneGraph = bridge.exportSceneGraph();
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
if (!graph) {
|
|
67
|
+
throwMcpError(ErrorCode.InvalidParams, 'graph_required: pass `graph` when includeCurrentScene is false');
|
|
68
|
+
}
|
|
69
|
+
// Security: revalidate every node with AnyNode schema (including the
|
|
70
|
+
// AssetUrl allowlist) BEFORE persisting. Without this, the save_scene
|
|
71
|
+
// graph arg is a bypass for the URL hardening in A7. See P4 report.
|
|
72
|
+
const rawNodes = graph.nodes;
|
|
73
|
+
if (!rawNodes || typeof rawNodes !== 'object') {
|
|
74
|
+
throwMcpError(ErrorCode.InvalidParams, 'graph.nodes must be an object');
|
|
75
|
+
}
|
|
76
|
+
const errors = [];
|
|
77
|
+
for (const [nodeId, node] of Object.entries(rawNodes)) {
|
|
78
|
+
const res = AnyNode.safeParse(node);
|
|
79
|
+
if (!res.success) {
|
|
80
|
+
for (const issue of res.error.issues) {
|
|
81
|
+
errors.push({
|
|
82
|
+
nodeId,
|
|
83
|
+
path: issue.path.map(String).join('.'),
|
|
84
|
+
message: issue.message,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (errors.length > 0) {
|
|
90
|
+
throwMcpError(ErrorCode.InvalidParams, 'graph_invalid', { errors });
|
|
91
|
+
}
|
|
92
|
+
sceneGraph = graph;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const meta = await bridge.saveScene({
|
|
96
|
+
...(id !== undefined ? { id } : {}),
|
|
97
|
+
name,
|
|
98
|
+
...(projectId !== undefined ? { projectId } : {}),
|
|
99
|
+
graph: sceneGraph,
|
|
100
|
+
...(thumbnail !== undefined ? { thumbnailUrl: thumbnail } : {}),
|
|
101
|
+
...(expectedVersion !== undefined ? { expectedVersion } : {}),
|
|
102
|
+
saveMode,
|
|
103
|
+
...(publish !== undefined ? { publish } : {}),
|
|
104
|
+
operation: 'save_scene',
|
|
105
|
+
});
|
|
106
|
+
await appendLiveSceneEvent(bridge, meta.id, meta.version, 'save_scene', sceneGraph);
|
|
107
|
+
if (includeCurrentScene) {
|
|
108
|
+
bridge.setActiveScene(meta);
|
|
109
|
+
}
|
|
110
|
+
const payload = {
|
|
111
|
+
...sceneMetaPayload(meta, sceneGraph),
|
|
112
|
+
...currentLevelContext(bridge),
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: 'text', text: JSON.stringify(payload) }],
|
|
116
|
+
structuredContent: payload,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (err instanceof SceneVersionConflictError) {
|
|
121
|
+
throwMcpError(ErrorCode.InvalidRequest, 'version_conflict', {
|
|
122
|
+
expectedVersion,
|
|
123
|
+
id,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
127
|
+
throwMcpError(ErrorCode.InvalidRequest, msg);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { SceneBridge } from '../../bridge/scene-bridge';
|
|
2
|
+
import { type SceneOperations } from '../../operations';
|
|
3
|
+
import { type ProjectCreateOptions, type ProjectStatus, type SceneListOptions, type SceneMeta, type SceneMutateOptions, type SceneSaveOptions, type SceneStore, type SceneWithGraph } from '../../storage/types';
|
|
4
|
+
export type StoredTextContent = {
|
|
5
|
+
type: string;
|
|
6
|
+
text: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function parseToolText(content: StoredTextContent[]): Record<string, unknown>;
|
|
9
|
+
export declare function createTestSceneOperations(options?: {
|
|
10
|
+
bridge?: SceneBridge;
|
|
11
|
+
store?: InMemorySceneStore;
|
|
12
|
+
}): {
|
|
13
|
+
bridge: SceneBridge;
|
|
14
|
+
store: InMemorySceneStore;
|
|
15
|
+
operations: SceneOperations;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* In-memory `SceneStore` for tests. Backed by a plain `Map` keyed by id.
|
|
19
|
+
* Implements the full interface including optimistic concurrency via
|
|
20
|
+
* `expectedVersion`.
|
|
21
|
+
*/
|
|
22
|
+
export declare class InMemorySceneStore implements SceneStore {
|
|
23
|
+
readonly backend: "sqlite";
|
|
24
|
+
private readonly data;
|
|
25
|
+
private readonly projects;
|
|
26
|
+
private idCounter;
|
|
27
|
+
private projectCounter;
|
|
28
|
+
createProject(opts: ProjectCreateOptions): Promise<ProjectStatus>;
|
|
29
|
+
getProjectStatus(id: string): Promise<ProjectStatus | null>;
|
|
30
|
+
save(opts: SceneSaveOptions): Promise<SceneMeta>;
|
|
31
|
+
load(id: string): Promise<SceneWithGraph | null>;
|
|
32
|
+
list(opts?: SceneListOptions): Promise<SceneMeta[]>;
|
|
33
|
+
delete(id: string, opts?: SceneMutateOptions): Promise<boolean>;
|
|
34
|
+
rename(id: string, newName: string, opts?: SceneMutateOptions): Promise<SceneMeta>;
|
|
35
|
+
private touchProject;
|
|
36
|
+
private toMeta;
|
|
37
|
+
private toProjectStatus;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=test-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../../src/tools/scene-lifecycle/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AACvD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAC9E,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAEf,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAA;AAG5B,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9D,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEnF;AAED,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE;IAClD,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,KAAK,CAAC,EAAE,kBAAkB,CAAA;CAC3B,GAAG;IACF,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,kBAAkB,CAAA;IACzB,UAAU,EAAE,eAAe,CAAA;CAC5B,CAKA;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,UAAU;IACnD,QAAQ,CAAC,OAAO,EAAG,QAAQ,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAWtB;IACH,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,cAAc,CAAI;IAEpB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC;IAejE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAK3D,IAAI,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC;IA+DhD,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAShD,IAAI,CAAC,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAanD,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAW/D,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,SAAS,CAAC;IAmBxF,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,MAAM;IAoBd,OAAO,CAAC,eAAe;CA0BxB"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { SceneBridge } from '../../bridge/scene-bridge';
|
|
2
|
+
import { createSceneOperations } from '../../operations';
|
|
3
|
+
import { SceneNotFoundError, SceneVersionConflictError, } from '../../storage/types';
|
|
4
|
+
import { computeGraphHash, editorUrlFor } from './metadata';
|
|
5
|
+
export function parseToolText(content) {
|
|
6
|
+
return JSON.parse(content[0].text);
|
|
7
|
+
}
|
|
8
|
+
export function createTestSceneOperations(options) {
|
|
9
|
+
const bridge = options?.bridge ?? new SceneBridge();
|
|
10
|
+
const store = options?.store ?? new InMemorySceneStore();
|
|
11
|
+
const operations = createSceneOperations({ bridge, store });
|
|
12
|
+
return { bridge, store, operations };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* In-memory `SceneStore` for tests. Backed by a plain `Map` keyed by id.
|
|
16
|
+
* Implements the full interface including optimistic concurrency via
|
|
17
|
+
* `expectedVersion`.
|
|
18
|
+
*/
|
|
19
|
+
export class InMemorySceneStore {
|
|
20
|
+
backend = 'sqlite';
|
|
21
|
+
data = new Map();
|
|
22
|
+
projects = new Map();
|
|
23
|
+
idCounter = 0;
|
|
24
|
+
projectCounter = 0;
|
|
25
|
+
async createProject(opts) {
|
|
26
|
+
const id = opts.id ?? `project_${++this.projectCounter}`;
|
|
27
|
+
const now = new Date().toISOString();
|
|
28
|
+
this.projects.set(id, {
|
|
29
|
+
id,
|
|
30
|
+
name: opts.name,
|
|
31
|
+
ownerId: opts.ownerId ?? null,
|
|
32
|
+
isPrivate: opts.isPrivate ?? true,
|
|
33
|
+
thumbnailUrl: null,
|
|
34
|
+
createdAt: now,
|
|
35
|
+
updatedAt: now,
|
|
36
|
+
});
|
|
37
|
+
return this.toProjectStatus(id);
|
|
38
|
+
}
|
|
39
|
+
async getProjectStatus(id) {
|
|
40
|
+
if (!(this.projects.has(id) || this.data.has(id)))
|
|
41
|
+
return null;
|
|
42
|
+
return this.toProjectStatus(id);
|
|
43
|
+
}
|
|
44
|
+
async save(opts) {
|
|
45
|
+
const existing = opts.id ? this.data.get(opts.id) : undefined;
|
|
46
|
+
if (existing) {
|
|
47
|
+
if (opts.expectedVersion !== undefined && existing.version !== opts.expectedVersion) {
|
|
48
|
+
throw new SceneVersionConflictError(`Expected version ${opts.expectedVersion}, have ${existing.version}`);
|
|
49
|
+
}
|
|
50
|
+
const now = new Date().toISOString();
|
|
51
|
+
const nodeCount = Object.keys(opts.graph.nodes ?? {}).length;
|
|
52
|
+
const serialized = JSON.stringify(opts.graph);
|
|
53
|
+
const updated = {
|
|
54
|
+
id: existing.id,
|
|
55
|
+
name: opts.name,
|
|
56
|
+
projectId: opts.projectId ?? existing.projectId,
|
|
57
|
+
thumbnailUrl: opts.thumbnailUrl ?? existing.thumbnailUrl,
|
|
58
|
+
version: existing.version + 1,
|
|
59
|
+
createdAt: existing.createdAt,
|
|
60
|
+
updatedAt: now,
|
|
61
|
+
ownerId: opts.ownerId ?? existing.ownerId,
|
|
62
|
+
sizeBytes: serialized.length,
|
|
63
|
+
nodeCount,
|
|
64
|
+
editorUrl: existing.editorUrl ?? `/editor/${existing.id}`,
|
|
65
|
+
url: existing.url ?? `/editor/${existing.id}`,
|
|
66
|
+
published: true,
|
|
67
|
+
graphHash: computeGraphHash(opts.graph),
|
|
68
|
+
graph: opts.graph,
|
|
69
|
+
};
|
|
70
|
+
this.data.set(existing.id, updated);
|
|
71
|
+
this.touchProject(existing.id, opts.name, updated.updatedAt);
|
|
72
|
+
return this.toMeta(updated);
|
|
73
|
+
}
|
|
74
|
+
if (opts.expectedVersion !== undefined) {
|
|
75
|
+
throw new SceneVersionConflictError('Cannot pass expectedVersion for a new scene');
|
|
76
|
+
}
|
|
77
|
+
const id = opts.id ?? `scene_${++this.idCounter}`;
|
|
78
|
+
const now = new Date().toISOString();
|
|
79
|
+
const serialized = JSON.stringify(opts.graph);
|
|
80
|
+
const nodeCount = Object.keys(opts.graph.nodes ?? {}).length;
|
|
81
|
+
const record = {
|
|
82
|
+
id,
|
|
83
|
+
name: opts.name,
|
|
84
|
+
projectId: opts.projectId ?? (this.projects.has(id) ? id : null),
|
|
85
|
+
thumbnailUrl: opts.thumbnailUrl ?? null,
|
|
86
|
+
version: 1,
|
|
87
|
+
createdAt: now,
|
|
88
|
+
updatedAt: now,
|
|
89
|
+
ownerId: opts.ownerId ?? null,
|
|
90
|
+
sizeBytes: serialized.length,
|
|
91
|
+
nodeCount,
|
|
92
|
+
editorUrl: `/editor/${id}`,
|
|
93
|
+
url: `/editor/${id}`,
|
|
94
|
+
published: true,
|
|
95
|
+
graphHash: computeGraphHash(opts.graph),
|
|
96
|
+
graph: opts.graph,
|
|
97
|
+
};
|
|
98
|
+
this.data.set(id, record);
|
|
99
|
+
this.touchProject(id, opts.name, now);
|
|
100
|
+
return this.toMeta(record);
|
|
101
|
+
}
|
|
102
|
+
async load(id) {
|
|
103
|
+
const rec = this.data.get(id);
|
|
104
|
+
if (!rec)
|
|
105
|
+
return null;
|
|
106
|
+
return {
|
|
107
|
+
...rec,
|
|
108
|
+
graph: JSON.parse(JSON.stringify(rec.graph)),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async list(opts) {
|
|
112
|
+
let scenes = Array.from(this.data.values()).map((r) => this.toMeta(r));
|
|
113
|
+
if (opts?.projectId !== undefined) {
|
|
114
|
+
scenes = scenes.filter((s) => s.projectId === opts.projectId);
|
|
115
|
+
}
|
|
116
|
+
if (opts?.ownerId !== undefined) {
|
|
117
|
+
scenes = scenes.filter((s) => s.ownerId === opts.ownerId);
|
|
118
|
+
}
|
|
119
|
+
scenes.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1));
|
|
120
|
+
if (opts?.limit !== undefined)
|
|
121
|
+
scenes = scenes.slice(0, opts.limit);
|
|
122
|
+
return scenes;
|
|
123
|
+
}
|
|
124
|
+
async delete(id, opts) {
|
|
125
|
+
const rec = this.data.get(id);
|
|
126
|
+
if (!rec)
|
|
127
|
+
throw new SceneNotFoundError(`Scene ${id} not found`);
|
|
128
|
+
if (opts?.expectedVersion !== undefined && rec.version !== opts.expectedVersion) {
|
|
129
|
+
throw new SceneVersionConflictError(`Expected version ${opts.expectedVersion}, have ${rec.version}`);
|
|
130
|
+
}
|
|
131
|
+
return this.data.delete(id);
|
|
132
|
+
}
|
|
133
|
+
async rename(id, newName, opts) {
|
|
134
|
+
const rec = this.data.get(id);
|
|
135
|
+
if (!rec)
|
|
136
|
+
throw new SceneNotFoundError(`Scene ${id} not found`);
|
|
137
|
+
if (opts?.expectedVersion !== undefined && rec.version !== opts.expectedVersion) {
|
|
138
|
+
throw new SceneVersionConflictError(`Expected version ${opts.expectedVersion}, have ${rec.version}`);
|
|
139
|
+
}
|
|
140
|
+
const updated = {
|
|
141
|
+
...rec,
|
|
142
|
+
name: newName,
|
|
143
|
+
version: rec.version + 1,
|
|
144
|
+
updatedAt: new Date().toISOString(),
|
|
145
|
+
};
|
|
146
|
+
this.data.set(id, updated);
|
|
147
|
+
this.touchProject(id, newName, updated.updatedAt);
|
|
148
|
+
return this.toMeta(updated);
|
|
149
|
+
}
|
|
150
|
+
touchProject(id, name, updatedAt) {
|
|
151
|
+
const existing = this.projects.get(id);
|
|
152
|
+
if (existing) {
|
|
153
|
+
this.projects.set(id, { ...existing, name, updatedAt });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.projects.set(id, {
|
|
157
|
+
id,
|
|
158
|
+
name,
|
|
159
|
+
ownerId: null,
|
|
160
|
+
isPrivate: true,
|
|
161
|
+
thumbnailUrl: null,
|
|
162
|
+
createdAt: updatedAt,
|
|
163
|
+
updatedAt,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
toMeta(rec) {
|
|
167
|
+
const editorUrl = editorUrlFor(rec);
|
|
168
|
+
return {
|
|
169
|
+
id: rec.id,
|
|
170
|
+
name: rec.name,
|
|
171
|
+
projectId: rec.projectId,
|
|
172
|
+
thumbnailUrl: rec.thumbnailUrl,
|
|
173
|
+
version: rec.version,
|
|
174
|
+
createdAt: rec.createdAt,
|
|
175
|
+
updatedAt: rec.updatedAt,
|
|
176
|
+
ownerId: rec.ownerId,
|
|
177
|
+
sizeBytes: rec.sizeBytes,
|
|
178
|
+
nodeCount: rec.nodeCount,
|
|
179
|
+
editorUrl,
|
|
180
|
+
url: editorUrl,
|
|
181
|
+
published: rec.published ?? true,
|
|
182
|
+
graphHash: rec.graphHash ?? computeGraphHash(rec.graph),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
toProjectStatus(id) {
|
|
186
|
+
const project = this.projects.get(id);
|
|
187
|
+
const scene = this.data.get(id);
|
|
188
|
+
const now = new Date().toISOString();
|
|
189
|
+
const editorUrl = `/editor/${id}`;
|
|
190
|
+
return {
|
|
191
|
+
id,
|
|
192
|
+
projectId: id,
|
|
193
|
+
name: scene?.name ?? project?.name ?? id,
|
|
194
|
+
editorUrl,
|
|
195
|
+
url: editorUrl,
|
|
196
|
+
ownerId: scene?.ownerId ?? project?.ownerId ?? null,
|
|
197
|
+
thumbnailUrl: scene?.thumbnailUrl ?? project?.thumbnailUrl ?? null,
|
|
198
|
+
publishedVersion: scene?.version ?? null,
|
|
199
|
+
latestVersion: scene?.version ?? null,
|
|
200
|
+
draftVersion: null,
|
|
201
|
+
browserVisibleVersion: scene?.version ?? null,
|
|
202
|
+
version: scene?.version ?? 0,
|
|
203
|
+
isEmpty: !scene || scene.nodeCount === 0,
|
|
204
|
+
sizeBytes: scene?.sizeBytes ?? 0,
|
|
205
|
+
nodeCount: scene?.nodeCount ?? 0,
|
|
206
|
+
graphHash: scene?.graphHash ?? (scene ? computeGraphHash(scene.graph) : null),
|
|
207
|
+
createdAt: scene?.createdAt ?? project?.createdAt ?? now,
|
|
208
|
+
updatedAt: scene?.updatedAt ?? project?.updatedAt ?? now,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { SceneOperations } from '../operations';
|
|
4
|
+
export declare const levelScopedInput: {
|
|
5
|
+
levelId: z.ZodOptional<z.ZodString>;
|
|
6
|
+
};
|
|
7
|
+
export declare const listLevelsOutput: {
|
|
8
|
+
activeSceneId: z.ZodNullable<z.ZodString>;
|
|
9
|
+
levelCount: z.ZodNumber;
|
|
10
|
+
occupiedStoryCount: z.ZodNumber;
|
|
11
|
+
supportLevelCount: z.ZodNumber;
|
|
12
|
+
roofLevelIds: z.ZodArray<z.ZodString>;
|
|
13
|
+
levels: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
14
|
+
};
|
|
15
|
+
export declare const getLevelSummaryOutput: {
|
|
16
|
+
levelId: z.ZodString;
|
|
17
|
+
levelName: z.ZodOptional<z.ZodString>;
|
|
18
|
+
role: z.ZodString;
|
|
19
|
+
metadataRole: z.ZodNullable<z.ZodString>;
|
|
20
|
+
isOccupiedStory: z.ZodBoolean;
|
|
21
|
+
isSupportLevel: z.ZodBoolean;
|
|
22
|
+
referenceLevelId: z.ZodNullable<z.ZodString>;
|
|
23
|
+
counts: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
24
|
+
walls: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
25
|
+
zones: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
26
|
+
items: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
27
|
+
slabs: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
28
|
+
ceilings: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
29
|
+
};
|
|
30
|
+
export declare const getWallsOutput: {
|
|
31
|
+
levelId: z.ZodString;
|
|
32
|
+
walls: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
33
|
+
};
|
|
34
|
+
export declare const getZonesOutput: {
|
|
35
|
+
levelId: z.ZodString;
|
|
36
|
+
zones: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
37
|
+
};
|
|
38
|
+
export declare const verifySceneOutput: {
|
|
39
|
+
ok: z.ZodBoolean;
|
|
40
|
+
valid: z.ZodBoolean;
|
|
41
|
+
levelCount: z.ZodNumber;
|
|
42
|
+
occupiedStoryCount: z.ZodNumber;
|
|
43
|
+
supportLevelCount: z.ZodNumber;
|
|
44
|
+
roofLevelIds: z.ZodArray<z.ZodString>;
|
|
45
|
+
activeSceneId: z.ZodNullable<z.ZodString>;
|
|
46
|
+
levels: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
47
|
+
emptyLevelIds: z.ZodArray<z.ZodString>;
|
|
48
|
+
issues: z.ZodArray<z.ZodString>;
|
|
49
|
+
hasIssues: z.ZodBoolean;
|
|
50
|
+
};
|
|
51
|
+
export declare function registerListLevels(server: McpServer, bridge: SceneOperations): void;
|
|
52
|
+
export declare function registerGetLevelSummary(server: McpServer, bridge: SceneOperations): void;
|
|
53
|
+
export declare function registerGetWalls(server: McpServer, bridge: SceneOperations): void;
|
|
54
|
+
export declare function registerGetZones(server: McpServer, bridge: SceneOperations): void;
|
|
55
|
+
export declare function registerVerifyScene(server: McpServer, bridge: SceneOperations): void;
|
|
56
|
+
export declare function registerSceneQueryTools(server: McpServer, bridge: SceneOperations): void;
|
|
57
|
+
//# sourceMappingURL=scene-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-query.d.ts","sourceRoot":"","sources":["../../src/tools/scene-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAWpD,eAAO,MAAM,gBAAgB;;CAE5B,CAAA;AAID,eAAO,MAAM,gBAAgB;;;;;;;CAO5B,CAAA;AAED,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;CAcjC,CAAA;AAED,eAAO,MAAM,cAAc;;;CAG1B,CAAA;AAED,eAAO,MAAM,cAAc;;;CAG1B,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;CAY7B,CAAA;AA2YD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAyCnF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAgBxF;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAmBjF;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAmBjF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAkQpF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAMxF"}
|