@ifc-lite/viewer 1.19.0 → 1.19.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.
- package/.turbo/turbo-build.log +15 -14
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +8 -0
- package/dist/assets/basketViewActivator-CA2CTcVo.js +71 -0
- package/dist/assets/{bcf-DOG9_WPX.js → bcf-4K724hw0.js} +18 -18
- package/dist/assets/{exporters-BraHBeoi.js → exporters-xbXqEDlO.js} +53 -46
- package/dist/assets/ids-2WdONLlu.js +2033 -0
- package/dist/assets/index-BXeEKqJG.css +1 -0
- package/dist/assets/{index-BOi3BuUI.js → index-D8Epw-e7.js} +48072 -30928
- package/dist/assets/{native-bridge-CpBeOPQa.js → native-bridge-DKmx1z95.js} +2 -2
- package/dist/assets/{sandbox-Baez7n-t.js → sandbox-tccwm5Bo.js} +547 -529
- package/dist/assets/{server-client-BB6cMAXE.js → server-client-LoWPK1N2.js} +1 -1
- package/dist/assets/three-CDRZThFA.js +4057 -0
- package/dist/assets/{wasm-bridge-CAYCUHbE.js → wasm-bridge-BsJGgPMs.js} +1 -1
- package/dist/index.html +8 -7
- package/dist/samples/building-architecture.ifc +453 -0
- package/dist/samples/hello-wall.ifc +1054 -0
- package/dist/samples/infra-bridge.ifc +962 -0
- package/package.json +7 -2
- package/public/samples/building-architecture.ifc +453 -0
- package/public/samples/hello-wall.ifc +1054 -0
- package/public/samples/infra-bridge.ifc +962 -0
- package/src/App.tsx +37 -3
- package/src/components/mcp/HeroScene.tsx +876 -0
- package/src/components/mcp/McpLanding.tsx +1318 -0
- package/src/components/mcp/McpPlayground.tsx +524 -0
- package/src/components/mcp/PlaygroundChat.tsx +1097 -0
- package/src/components/mcp/PlaygroundViewer.tsx +815 -0
- package/src/components/mcp/README.md +171 -0
- package/src/components/mcp/data.ts +659 -0
- package/src/components/mcp/playground-dispatcher.ts +1649 -0
- package/src/components/mcp/playground-files.ts +107 -0
- package/src/components/mcp/playground-uploads.ts +122 -0
- package/src/components/mcp/types.ts +65 -0
- package/src/components/mcp/use-mcp-page.ts +109 -0
- package/src/components/viewer/MainToolbar.tsx +19 -0
- package/src/components/viewer/ViewportContainer.tsx +35 -4
- package/src/generated/mcp-catalog.json +82 -0
- package/vite.config.ts +6 -0
- package/dist/assets/basketViewActivator-RZy5c3Td.js +0 -1
- package/dist/assets/ids-DQ5jY0E8.js +0 -1
- package/dist/assets/index-0XpVr_S5.css +0 -1
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Static data shared by all three landing-page variants:
|
|
7
|
+
*
|
|
8
|
+
* • CATALOG — the auto-generated tool list (typed import of the JSON
|
|
9
|
+
* emitted by `node packages/mcp/dist/cli.js --dump-tools`).
|
|
10
|
+
* Until that script lands, `mcp-catalog.json` is hand-
|
|
11
|
+
* seeded with the real shape of the v0.1 surface so the
|
|
12
|
+
* UI is real.
|
|
13
|
+
* • CATEGORIES — display order + short blurb per category.
|
|
14
|
+
* • CLIENTS — the install-grid targets (Claude Desktop, Cursor,
|
|
15
|
+
* Windsurf, VS Code, Goose).
|
|
16
|
+
* • RECIPES — copy-pasteable starter prompts.
|
|
17
|
+
* • makeConfig… — builds the JSON config snippet & deep-link URL each
|
|
18
|
+
* client expects, so the install Dialog is one source
|
|
19
|
+
* of truth instead of three.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import catalogJson from '@/generated/mcp-catalog.json';
|
|
23
|
+
import type {
|
|
24
|
+
CatalogTool,
|
|
25
|
+
McpCatalog,
|
|
26
|
+
McpClient,
|
|
27
|
+
McpClientId,
|
|
28
|
+
McpRecipe,
|
|
29
|
+
ToolCategory,
|
|
30
|
+
} from './types';
|
|
31
|
+
|
|
32
|
+
export const CATALOG: McpCatalog = catalogJson as McpCatalog;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The version we advertise on the page. Read from the catalog if present,
|
|
36
|
+
* else fall back to a sensible default. (Kept here so the three landings
|
|
37
|
+
* agree without re-reading the JSON.)
|
|
38
|
+
*/
|
|
39
|
+
export const MCP_VERSION = CATALOG.version ?? '0.1.0';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Display order. Maps to the "package.json scripts" mental model so users
|
|
43
|
+
* scanning the catalog land on Discovery → Query first (most common entry
|
|
44
|
+
* points), then escalating capability tiers.
|
|
45
|
+
*/
|
|
46
|
+
export const CATEGORY_ORDER: ToolCategory[] = [
|
|
47
|
+
'Discovery',
|
|
48
|
+
'Query',
|
|
49
|
+
'Geometry',
|
|
50
|
+
'Validation',
|
|
51
|
+
'Mutation',
|
|
52
|
+
'BCF',
|
|
53
|
+
'bSDD',
|
|
54
|
+
'Diff',
|
|
55
|
+
'Export',
|
|
56
|
+
'Viewer',
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
export const CATEGORY_BLURBS: Record<ToolCategory, string> = {
|
|
60
|
+
Discovery: 'Models, schema, what’s loaded.',
|
|
61
|
+
Query: 'Find entities, properties, materials, classifications.',
|
|
62
|
+
Geometry: 'Bounding boxes, volumes, areas — read from quantity sets.',
|
|
63
|
+
Validation: 'IDS specs and a built-in model audit.',
|
|
64
|
+
Mutation: 'Queue property/attribute writes; persist on save.',
|
|
65
|
+
BCF: 'Author and export buildingSMART issues + viewpoints.',
|
|
66
|
+
bSDD: 'Look up canonical class/property metadata.',
|
|
67
|
+
Diff: 'Compare two loaded models — added/removed/changed.',
|
|
68
|
+
Export: 'Dump to .ifc, CSV, JSON, glTF, IFCx, PDF.',
|
|
69
|
+
Viewer: 'Drive the live WebGL viewer; subscribe to user picks.',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const CATEGORY_GLYPHS: Record<ToolCategory, string> = {
|
|
73
|
+
Discovery: '01',
|
|
74
|
+
Query: '02',
|
|
75
|
+
Geometry: '03',
|
|
76
|
+
Validation: '04',
|
|
77
|
+
Mutation: '05',
|
|
78
|
+
BCF: '06',
|
|
79
|
+
bSDD: '07',
|
|
80
|
+
Diff: '08',
|
|
81
|
+
Export: '09',
|
|
82
|
+
Viewer: '10',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export function toolsByCategory(): Map<ToolCategory, CatalogTool[]> {
|
|
86
|
+
const map = new Map<ToolCategory, CatalogTool[]>();
|
|
87
|
+
for (const cat of CATEGORY_ORDER) map.set(cat, []);
|
|
88
|
+
for (const tool of CATALOG.tools) {
|
|
89
|
+
const list = map.get(tool.category) ?? [];
|
|
90
|
+
list.push(tool);
|
|
91
|
+
map.set(tool.category, list);
|
|
92
|
+
}
|
|
93
|
+
for (const [cat, list] of map) {
|
|
94
|
+
list.sort((a, b) => a.name.localeCompare(b.name));
|
|
95
|
+
map.set(cat, list);
|
|
96
|
+
}
|
|
97
|
+
return map;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── install grid ────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
export const CLIENTS: McpClient[] = [
|
|
103
|
+
{
|
|
104
|
+
id: 'claude-desktop',
|
|
105
|
+
name: 'Claude Desktop',
|
|
106
|
+
blurb: 'Drop into the desktop config. Restart, ask Claude.',
|
|
107
|
+
configHint: '~/Library/Application Support/Claude/claude_desktop_config.json',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'cursor',
|
|
111
|
+
name: 'Cursor',
|
|
112
|
+
blurb: 'One-click install via deep link, or paste into mcp.json.',
|
|
113
|
+
deepLinkPrefix: 'cursor://anysphere.cursor-deeplink/mcp/install',
|
|
114
|
+
configHint: '~/.cursor/mcp.json',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'windsurf',
|
|
118
|
+
name: 'Windsurf',
|
|
119
|
+
blurb: 'Same shape as Cursor; deep link supported in 1.4+.',
|
|
120
|
+
deepLinkPrefix: 'windsurf://mcp/install',
|
|
121
|
+
configHint: '~/.codeium/windsurf/mcp_config.json',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'vscode',
|
|
125
|
+
name: 'VS Code',
|
|
126
|
+
blurb: 'GitHub Copilot Chat, agent mode. Adds an MCP entry.',
|
|
127
|
+
deepLinkPrefix: 'vscode:mcp/install',
|
|
128
|
+
configHint: '.vscode/mcp.json (workspace) or settings.json',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'goose',
|
|
132
|
+
name: 'Goose',
|
|
133
|
+
blurb: 'Block’s open-source agent. CLI install.',
|
|
134
|
+
configHint: '~/.config/goose/profiles.yaml',
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
/** Build the canonical JSON snippet for each client. */
|
|
139
|
+
export function makeConfigSnippet(client: McpClientId): string {
|
|
140
|
+
const sample = '/abs/path/to/your/model.ifc';
|
|
141
|
+
switch (client) {
|
|
142
|
+
case 'claude-desktop':
|
|
143
|
+
return JSON.stringify(
|
|
144
|
+
{
|
|
145
|
+
mcpServers: {
|
|
146
|
+
'ifc-lite': {
|
|
147
|
+
command: 'npx',
|
|
148
|
+
args: ['-y', '@ifc-lite/mcp', sample],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
null,
|
|
153
|
+
2,
|
|
154
|
+
);
|
|
155
|
+
case 'cursor':
|
|
156
|
+
return JSON.stringify(
|
|
157
|
+
{
|
|
158
|
+
mcpServers: {
|
|
159
|
+
'ifc-lite': {
|
|
160
|
+
command: 'npx',
|
|
161
|
+
args: ['-y', '@ifc-lite/mcp', sample],
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
null,
|
|
166
|
+
2,
|
|
167
|
+
);
|
|
168
|
+
case 'windsurf':
|
|
169
|
+
return JSON.stringify(
|
|
170
|
+
{
|
|
171
|
+
mcpServers: {
|
|
172
|
+
'ifc-lite': {
|
|
173
|
+
command: 'npx',
|
|
174
|
+
args: ['-y', '@ifc-lite/mcp', sample],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
null,
|
|
179
|
+
2,
|
|
180
|
+
);
|
|
181
|
+
case 'vscode':
|
|
182
|
+
return JSON.stringify(
|
|
183
|
+
{
|
|
184
|
+
servers: {
|
|
185
|
+
'ifc-lite': {
|
|
186
|
+
type: 'stdio',
|
|
187
|
+
command: 'npx',
|
|
188
|
+
args: ['-y', '@ifc-lite/mcp', sample],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
null,
|
|
193
|
+
2,
|
|
194
|
+
);
|
|
195
|
+
case 'goose':
|
|
196
|
+
return [
|
|
197
|
+
'# In ~/.config/goose/profiles.yaml',
|
|
198
|
+
'extensions:',
|
|
199
|
+
' ifc-lite:',
|
|
200
|
+
' cmd: npx',
|
|
201
|
+
' args: ["-y", "@ifc-lite/mcp", "/abs/path/to/your/model.ifc"]',
|
|
202
|
+
].join('\n');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Build the deep-link URL for clients that support one-click install.
|
|
208
|
+
* Returns null for clients that need manual config.
|
|
209
|
+
*/
|
|
210
|
+
export function makeDeepLink(client: McpClientId): string | null {
|
|
211
|
+
const c = CLIENTS.find((x) => x.id === client);
|
|
212
|
+
if (!c?.deepLinkPrefix) return null;
|
|
213
|
+
const config = {
|
|
214
|
+
name: 'ifc-lite',
|
|
215
|
+
command: 'npx',
|
|
216
|
+
args: ['-y', '@ifc-lite/mcp'],
|
|
217
|
+
};
|
|
218
|
+
const encoded = btoa(unescape(encodeURIComponent(JSON.stringify(config))));
|
|
219
|
+
return `${c.deepLinkPrefix}?name=ifc-lite&config=${encoded}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── recipes ─────────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
export const RECIPES: McpRecipe[] = [
|
|
225
|
+
{
|
|
226
|
+
id: 'audit-fire-ratings',
|
|
227
|
+
title: 'Audit fire-rating coverage',
|
|
228
|
+
family: 'audit',
|
|
229
|
+
prompt:
|
|
230
|
+
'Run model_audit, then for every IfcWall and IfcDoor with no FireRating in Pset_WallCommon / Pset_DoorCommon, list the GlobalId and storey. Group by storey.',
|
|
231
|
+
uses: ['model_audit', 'query_entities', 'properties_unique'],
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: 'visualize-external-walls',
|
|
235
|
+
title: 'Visualize all external walls',
|
|
236
|
+
family: 'visualize',
|
|
237
|
+
prompt:
|
|
238
|
+
'Open the viewer, isolate IfcWall where Pset_WallCommon.IsExternal=true, color them red, frame the camera on the bounds.',
|
|
239
|
+
uses: ['viewer_open', 'viewer_isolate', 'viewer_colorize', 'viewer_fly_to'],
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: 'validate-ids',
|
|
243
|
+
title: 'Validate against this IDS spec',
|
|
244
|
+
family: 'validate',
|
|
245
|
+
prompt:
|
|
246
|
+
'Validate the model against ./specs/lod350-walls.ids. Summarize the failing specifications and color the offending entities red in the viewer.',
|
|
247
|
+
uses: ['ids_validate', 'ids_explain', 'viewer_colorize'],
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: 'narrow-doors',
|
|
251
|
+
title: 'BCF every door under 80 cm',
|
|
252
|
+
family: 'author',
|
|
253
|
+
prompt:
|
|
254
|
+
'Find every IfcDoor whose OverallWidth < 0.80m. For each, create a BCF topic with priority=Major and a viewpoint that selects only that door. Export the .bcfzip when done.',
|
|
255
|
+
uses: ['query_entities', 'bcf_topic_create', 'bcf_viewpoint_create', 'bcf_export'],
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: 'diff-versions',
|
|
259
|
+
title: 'Diff two model revisions',
|
|
260
|
+
family: 'compare',
|
|
261
|
+
prompt:
|
|
262
|
+
'Load arch.v3.ifc as model "v3" and arch.v4.ifc as model "v4". Run model_diff and quantity_diff(IfcWall, Volume). Tell me what walls were added or modified.',
|
|
263
|
+
uses: ['model_load', 'model_diff', 'quantity_diff'],
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: 'bsdd-properties',
|
|
267
|
+
title: 'Lookup bSDD properties for IfcWall',
|
|
268
|
+
family: 'discover',
|
|
269
|
+
prompt:
|
|
270
|
+
'Use bsdd_property_sets for IfcWall. List every Pset and the canonical property names + datatypes. Highlight any in our model that aren’t in the bSDD spec.',
|
|
271
|
+
uses: ['bsdd_property_sets', 'properties_unique', 'schema_describe'],
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: 'click-to-inspect',
|
|
275
|
+
title: 'Click to inspect',
|
|
276
|
+
family: 'visualize',
|
|
277
|
+
prompt:
|
|
278
|
+
'Open the viewer. When I click an entity, run viewer_describe_selection and tell me everything: attributes, properties, quantities, classifications, materials.',
|
|
279
|
+
uses: ['viewer_open', 'viewer_wait_for_selection', 'viewer_describe_selection'],
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: 'space-program',
|
|
283
|
+
title: 'Check the space program',
|
|
284
|
+
family: 'audit',
|
|
285
|
+
prompt:
|
|
286
|
+
'Group IfcSpace by storey, sum the area, and tell me which storeys are over/under their target gross area from Pset_SpaceProgram.',
|
|
287
|
+
uses: ['query_entities', 'count_entities', 'geometry_area'],
|
|
288
|
+
},
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
export const FAMILY_ACCENT: Record<McpRecipe['family'], string> = {
|
|
292
|
+
audit: '#d6ff3f',
|
|
293
|
+
visualize: '#7aa2f7',
|
|
294
|
+
validate: '#73daca',
|
|
295
|
+
author: '#ff9e64',
|
|
296
|
+
compare: '#bb9af7',
|
|
297
|
+
discover: '#9ece6a',
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// ── stats helpers ───────────────────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
export function catalogStats() {
|
|
303
|
+
const tools = CATALOG.tools;
|
|
304
|
+
const byScope = { read: 0, mutate: 0, export: 0 };
|
|
305
|
+
for (const t of tools) byScope[t.scope]++;
|
|
306
|
+
return {
|
|
307
|
+
total: tools.length,
|
|
308
|
+
categories: new Set(tools.map((t) => t.category)).size,
|
|
309
|
+
read: byScope.read,
|
|
310
|
+
mutate: byScope.mutate,
|
|
311
|
+
export: byScope.export,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ── parameter introspection ─────────────────────────────────────────────────
|
|
316
|
+
//
|
|
317
|
+
// The hand-seeded catalog only carries `type` + `required` for many tools
|
|
318
|
+
// (a richer schema dump will land once the CLI flag is wired). The catalog
|
|
319
|
+
// page still has to *show* something useful per tool, so we fall back to a
|
|
320
|
+
// curated map of parameter descriptions per tool — the agent-facing docs.
|
|
321
|
+
// When the live catalog gains real `properties` entries, the page prefers
|
|
322
|
+
// those and the fallback only fills in the gaps.
|
|
323
|
+
|
|
324
|
+
export interface ParamRow {
|
|
325
|
+
name: string;
|
|
326
|
+
type: string;
|
|
327
|
+
required: boolean;
|
|
328
|
+
description?: string;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const PARAM_FALLBACKS: Record<string, Array<Omit<ParamRow, 'required'>> & { required?: string[] }> = (() => {
|
|
332
|
+
type Row = Omit<ParamRow, 'required'>;
|
|
333
|
+
function pset(includeName = true): Row[] {
|
|
334
|
+
const r: Row[] = [
|
|
335
|
+
{ name: 'pset', type: 'string', description: 'Property set name (e.g. "Pset_WallCommon").' },
|
|
336
|
+
];
|
|
337
|
+
if (includeName) r.push({ name: 'name', type: 'string', description: 'Property name within the pset.' });
|
|
338
|
+
return r;
|
|
339
|
+
}
|
|
340
|
+
function refLocators(): Row[] {
|
|
341
|
+
return [
|
|
342
|
+
{ name: 'global_id', type: 'string', description: 'IFC GlobalId. Either this or express_id is required.' },
|
|
343
|
+
{ name: 'express_id', type: 'integer', description: 'STEP express id. Either this or global_id is required.' },
|
|
344
|
+
{ name: 'model_id', type: 'string', description: 'Optional when only one model is loaded.' },
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
model_info: [{ name: 'model_id', type: 'string', description: 'Optional; defaults to the only loaded model.' }],
|
|
349
|
+
model_load: [
|
|
350
|
+
{ name: 'file_path', type: 'string', description: 'Absolute path to the .ifc file.' },
|
|
351
|
+
{ name: 'model_id', type: 'string', description: 'Optional explicit ID; defaults to a slug of the file name.' },
|
|
352
|
+
],
|
|
353
|
+
model_unload: [{ name: 'model_id', type: 'string', description: 'Model to drop from the registry.' }],
|
|
354
|
+
schema_describe: [
|
|
355
|
+
{ name: 'type', type: 'string', description: 'IFC entity name, e.g. "IfcWall".' },
|
|
356
|
+
{ name: 'include_inherited', type: 'boolean', description: 'Include attributes from parent classes (default true).' },
|
|
357
|
+
],
|
|
358
|
+
query_entities: [
|
|
359
|
+
{ name: 'type', type: 'string', description: 'IFC entity type filter (e.g. "IfcWall").' },
|
|
360
|
+
{ name: 'limit', type: 'integer', description: 'Cap the result count. Default 100.' },
|
|
361
|
+
{ name: 'offset', type: 'integer', description: 'Skip this many matches before returning.' },
|
|
362
|
+
{ name: 'fields', type: 'string[]', description: 'Whitelist of fields per result (default returns the full shape).' },
|
|
363
|
+
{ name: 'in_storey', type: 'string', description: 'GlobalId of an IfcBuildingStorey to scope the query.' },
|
|
364
|
+
{ name: 'model_id', type: 'string' },
|
|
365
|
+
],
|
|
366
|
+
count_entities: [
|
|
367
|
+
{ name: 'group_by', type: '"type" | "storey" | "material"' },
|
|
368
|
+
{ name: 'type', type: 'string', description: 'Restrict the count to one IFC type.' },
|
|
369
|
+
{ name: 'model_id', type: 'string' },
|
|
370
|
+
],
|
|
371
|
+
get_entity: [...refLocators()],
|
|
372
|
+
get_entities_bulk: [
|
|
373
|
+
{ name: 'global_ids', type: 'string[]', description: 'Up to 200 GlobalIds to resolve.' },
|
|
374
|
+
{ name: 'model_id', type: 'string' },
|
|
375
|
+
],
|
|
376
|
+
properties_unique: [
|
|
377
|
+
{ name: 'type', type: 'string', description: 'IFC type to scan, e.g. "IfcWall".' },
|
|
378
|
+
...pset(),
|
|
379
|
+
{ name: 'property', type: 'string', description: 'Property name within the pset.' },
|
|
380
|
+
{ name: 'model_id', type: 'string' },
|
|
381
|
+
],
|
|
382
|
+
relationships: refLocators(),
|
|
383
|
+
containment_chain: refLocators(),
|
|
384
|
+
geometry_bbox: refLocators(),
|
|
385
|
+
geometry_volume: refLocators(),
|
|
386
|
+
geometry_area: refLocators(),
|
|
387
|
+
ids_validate: [
|
|
388
|
+
{ name: 'ids_path', type: 'string', description: 'Path to the .ids XML file to validate against.' },
|
|
389
|
+
{ name: 'model_id', type: 'string' },
|
|
390
|
+
],
|
|
391
|
+
ids_explain: [{ name: 'ids_path', type: 'string', description: 'Path to the .ids XML to summarise in plain language.' }],
|
|
392
|
+
entity_set_property: [
|
|
393
|
+
...refLocators(),
|
|
394
|
+
...pset(),
|
|
395
|
+
{ name: 'value', type: 'string | number | boolean', description: 'New value to write.' },
|
|
396
|
+
],
|
|
397
|
+
entity_delete_property: [...refLocators(), ...pset()],
|
|
398
|
+
entity_set_attribute: [
|
|
399
|
+
...refLocators(),
|
|
400
|
+
{ name: 'attribute', type: '"Name" | "Description" | "ObjectType" | "Tag"' },
|
|
401
|
+
{ name: 'value', type: 'string' },
|
|
402
|
+
],
|
|
403
|
+
entity_create: [
|
|
404
|
+
{ name: 'type', type: 'string', description: 'IFC entity to create, e.g. "IfcBuildingElementProxy".' },
|
|
405
|
+
{ name: 'attributes', type: 'unknown[]', description: 'Positional STEP attributes (strings, numbers, refs as "#42").' },
|
|
406
|
+
{ name: 'model_id', type: 'string' },
|
|
407
|
+
],
|
|
408
|
+
entity_delete: refLocators(),
|
|
409
|
+
mutation_batch: [
|
|
410
|
+
{ name: 'operations', type: '{ tool: string; args: object }[]', description: 'List of sub-tool ops to apply in order.' },
|
|
411
|
+
{ name: 'model_id', type: 'string' },
|
|
412
|
+
],
|
|
413
|
+
mutation_undo: [{ name: 'n', type: 'integer', description: 'Pop the last N pending mutations (default 1).' }],
|
|
414
|
+
model_save: [
|
|
415
|
+
{ name: 'file_path', type: 'string', description: 'Output .ifc path.' },
|
|
416
|
+
{ name: 'schema', type: '"IFC2X3" | "IFC4" | "IFC4X3"' },
|
|
417
|
+
{ name: 'model_id', type: 'string' },
|
|
418
|
+
],
|
|
419
|
+
bcf_topic_list: [{ name: 'status', type: 'string', description: 'Optional status filter (e.g. "Open").' }],
|
|
420
|
+
bcf_topic_create: [
|
|
421
|
+
{ name: 'title', type: 'string' },
|
|
422
|
+
{ name: 'description', type: 'string' },
|
|
423
|
+
{ name: 'priority', type: 'string' },
|
|
424
|
+
{ name: 'assigned_to', type: 'string' },
|
|
425
|
+
{ name: 'labels', type: 'string[]' },
|
|
426
|
+
],
|
|
427
|
+
bcf_topic_update: [
|
|
428
|
+
{ name: 'guid', type: 'string', description: 'BCF topic GUID.' },
|
|
429
|
+
{ name: 'status', type: 'string' },
|
|
430
|
+
{ name: 'priority', type: 'string' },
|
|
431
|
+
{ name: 'comment', type: 'string', description: 'Append a comment.' },
|
|
432
|
+
],
|
|
433
|
+
bcf_topic_close: [{ name: 'guid', type: 'string' }],
|
|
434
|
+
bcf_viewpoint_create: [
|
|
435
|
+
{ name: 'guid', type: 'string', description: 'Topic to attach the viewpoint to.' },
|
|
436
|
+
{ name: 'selection_global_ids', type: 'string[]' },
|
|
437
|
+
],
|
|
438
|
+
bcf_export: [{ name: 'file_path', type: 'string', description: 'Output .bcfzip path.' }],
|
|
439
|
+
bsdd_search: [{ name: 'query', type: 'string', description: 'Free-text search across all bSDD dictionaries.' }],
|
|
440
|
+
bsdd_class: [{ name: 'ifc_type', type: 'string', description: 'IFC entity name, e.g. "IfcWall".' }],
|
|
441
|
+
bsdd_property_sets: [{ name: 'ifc_type', type: 'string' }],
|
|
442
|
+
bsdd_match: refLocators(),
|
|
443
|
+
model_diff: [
|
|
444
|
+
{ name: 'a', type: 'string', description: 'model_id of the base.' },
|
|
445
|
+
{ name: 'b', type: 'string', description: 'model_id of the head.' },
|
|
446
|
+
{ name: 'by_entity', type: 'boolean', description: 'Include per-entity GlobalId additions/removals (default true).' },
|
|
447
|
+
],
|
|
448
|
+
quantity_diff: [
|
|
449
|
+
{ name: 'a', type: 'string' },
|
|
450
|
+
{ name: 'b', type: 'string' },
|
|
451
|
+
{ name: 'type', type: 'string', description: 'Default "IfcWall".' },
|
|
452
|
+
{ name: 'quantity', type: 'string', description: 'Default "Volume".' },
|
|
453
|
+
{ name: 'group_by', type: '"storey" | "type"' },
|
|
454
|
+
],
|
|
455
|
+
export_ifc: [
|
|
456
|
+
{ name: 'file_path', type: 'string' },
|
|
457
|
+
{ name: 'schema', type: '"IFC2X3" | "IFC4" | "IFC4X3"' },
|
|
458
|
+
{ name: 'global_ids', type: 'string[]', description: 'Optional GlobalId allowlist; defaults to the whole model.' },
|
|
459
|
+
],
|
|
460
|
+
export_csv: [
|
|
461
|
+
{ name: 'file_path', type: 'string' },
|
|
462
|
+
{ name: 'type', type: 'string', description: 'Filter by IFC type. Default: all products.' },
|
|
463
|
+
{ name: 'columns', type: 'string[]', description: 'Plain attributes or "Pset_X.Property" / "Qto_X.Quantity" paths.' },
|
|
464
|
+
{ name: 'separator', type: 'string', description: 'Default ",".' },
|
|
465
|
+
],
|
|
466
|
+
export_json: [
|
|
467
|
+
{ name: 'file_path', type: 'string' },
|
|
468
|
+
{ name: 'type', type: 'string' },
|
|
469
|
+
{ name: 'columns', type: 'string[]' },
|
|
470
|
+
],
|
|
471
|
+
viewer_open: [{ name: 'model_id', type: 'string' }],
|
|
472
|
+
viewer_colorize: [
|
|
473
|
+
{ name: 'global_ids', type: 'string[]' },
|
|
474
|
+
{ name: 'express_ids', type: 'integer[]' },
|
|
475
|
+
{ name: 'type', type: 'string' },
|
|
476
|
+
{ name: 'color', type: 'string | [r,g,b] | [r,g,b,a]', description: 'Hex (#ff8800), [0–1] tuple, or named colour.' },
|
|
477
|
+
{ name: 'model_id', type: 'string' },
|
|
478
|
+
],
|
|
479
|
+
viewer_isolate: [
|
|
480
|
+
{ name: 'global_ids', type: 'string[]' },
|
|
481
|
+
{ name: 'express_ids', type: 'integer[]' },
|
|
482
|
+
{ name: 'type', type: 'string' },
|
|
483
|
+
{ name: 'model_id', type: 'string' },
|
|
484
|
+
],
|
|
485
|
+
viewer_hide: [
|
|
486
|
+
{ name: 'global_ids', type: 'string[]' },
|
|
487
|
+
{ name: 'express_ids', type: 'integer[]' },
|
|
488
|
+
{ name: 'type', type: 'string' },
|
|
489
|
+
],
|
|
490
|
+
viewer_show: [
|
|
491
|
+
{ name: 'global_ids', type: 'string[]' },
|
|
492
|
+
{ name: 'express_ids', type: 'integer[]' },
|
|
493
|
+
{ name: 'type', type: 'string' },
|
|
494
|
+
],
|
|
495
|
+
viewer_fly_to: [
|
|
496
|
+
{ name: 'global_ids', type: 'string[]' },
|
|
497
|
+
{ name: 'express_ids', type: 'integer[]' },
|
|
498
|
+
],
|
|
499
|
+
viewer_set_section: [
|
|
500
|
+
{ name: 'axis', type: '"x" | "y" | "z"' },
|
|
501
|
+
{ name: 'position', type: 'number', description: 'Section plane offset along the chosen axis.' },
|
|
502
|
+
{ name: 'flipped', type: 'boolean' },
|
|
503
|
+
{ name: 'enabled', type: 'boolean' },
|
|
504
|
+
],
|
|
505
|
+
viewer_color_by_property: [
|
|
506
|
+
{ name: 'type', type: 'string' },
|
|
507
|
+
{ name: 'pset', type: 'string' },
|
|
508
|
+
{ name: 'property', type: 'string' },
|
|
509
|
+
{ name: 'missing_color', type: 'string', description: 'Colour for entities lacking the property. Default "gray".' },
|
|
510
|
+
],
|
|
511
|
+
viewer_get_selection: [
|
|
512
|
+
{ name: 'include', type: '("attributes"|"properties"|"quantities"|"classifications"|"materials")[]', description: 'Default ["attributes","classifications","materials"].' },
|
|
513
|
+
],
|
|
514
|
+
viewer_describe_selection: [],
|
|
515
|
+
viewer_wait_for_selection: [
|
|
516
|
+
{ name: 'timeout_ms', type: 'integer', description: 'Default 60000.' },
|
|
517
|
+
{ name: 'include', type: 'string[]' },
|
|
518
|
+
],
|
|
519
|
+
viewer_ask: [
|
|
520
|
+
{ name: 'reason', type: 'string', description: 'Why the agent wants the viewer open (used in the suggested wording).' },
|
|
521
|
+
{ name: 'model_id', type: 'string' },
|
|
522
|
+
],
|
|
523
|
+
};
|
|
524
|
+
})();
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Build a parameter table for a tool. Merges fields declared on the live
|
|
528
|
+
* `inputSchema.properties` with the curated `PARAM_FALLBACKS` map, so a
|
|
529
|
+
* partially-enriched schema (a few typed fields, no descriptions) still
|
|
530
|
+
* picks up the curated descriptions instead of rendering blank cells.
|
|
531
|
+
*
|
|
532
|
+
* Schema is the source of truth for `type`, `enum`, and `required`; the
|
|
533
|
+
* fallback only fills gaps in `description` and supplies any rows the
|
|
534
|
+
* schema didn't declare at all.
|
|
535
|
+
*/
|
|
536
|
+
export function paramsFor(tool: CatalogTool): ParamRow[] {
|
|
537
|
+
const schema = tool.inputSchema as { properties?: Record<string, { type?: string; description?: string; enum?: string[] }>; required?: string[] };
|
|
538
|
+
const required = new Set(schema?.required ?? []);
|
|
539
|
+
const fallback = PARAM_FALLBACKS[tool.name] ?? [];
|
|
540
|
+
const fallbackByName = new Map(fallback.map((row) => [row.name, row]));
|
|
541
|
+
|
|
542
|
+
const seen = new Set<string>();
|
|
543
|
+
const rows: ParamRow[] = [];
|
|
544
|
+
|
|
545
|
+
if (schema?.properties) {
|
|
546
|
+
for (const [name, def] of Object.entries(schema.properties)) {
|
|
547
|
+
const fb = fallbackByName.get(name);
|
|
548
|
+
const type = def?.enum
|
|
549
|
+
? def.enum.map((v) => JSON.stringify(v)).join(' | ')
|
|
550
|
+
: (def?.type ?? fb?.type ?? 'unknown');
|
|
551
|
+
rows.push({
|
|
552
|
+
name,
|
|
553
|
+
type,
|
|
554
|
+
required: required.has(name),
|
|
555
|
+
description: def?.description ?? fb?.description,
|
|
556
|
+
});
|
|
557
|
+
seen.add(name);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Append fallback-only rows (schemas that haven't enumerated every
|
|
562
|
+
// parameter yet — mostly the v0.5 stubs).
|
|
563
|
+
for (const row of fallback) {
|
|
564
|
+
if (seen.has(row.name)) continue;
|
|
565
|
+
rows.push({ ...row, required: required.has(row.name) });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return rows;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ── example invocations ─────────────────────────────────────────────────────
|
|
572
|
+
//
|
|
573
|
+
// One realistic example arg payload per tool. Used by the catalog page to
|
|
574
|
+
// show "this is what an agent would actually send" — far more useful than
|
|
575
|
+
// dumping the JSON Schema verbatim.
|
|
576
|
+
|
|
577
|
+
export const EXAMPLES: Record<string, Record<string, unknown>> = {
|
|
578
|
+
model_info: {},
|
|
579
|
+
model_list: {},
|
|
580
|
+
model_load: { file_path: '/abs/path/to/arch.ifc' },
|
|
581
|
+
model_unload: { model_id: 'arch' },
|
|
582
|
+
schema_describe: { type: 'IfcWall' },
|
|
583
|
+
query_entities: { type: 'IfcWall', limit: 25 },
|
|
584
|
+
count_entities: { group_by: 'type' },
|
|
585
|
+
get_entity: { global_id: '1AQAupaRP1txwK1AGiN61V' },
|
|
586
|
+
get_entities_bulk: { global_ids: ['1AQAupaRP1txwK1AGiN61V', '0u4wgLe6n0ABVaiXyikbkA'] },
|
|
587
|
+
spatial_hierarchy: {},
|
|
588
|
+
containment_chain: { global_id: '1AQAupaRP1txwK1AGiN61V' },
|
|
589
|
+
relationships: { global_id: '1AQAupaRP1txwK1AGiN61V' },
|
|
590
|
+
properties_unique: { type: 'IfcWall', pset: 'Pset_WallCommon', property: 'IsExternal' },
|
|
591
|
+
materials_list: {},
|
|
592
|
+
classifications_list: {},
|
|
593
|
+
georeferencing: {},
|
|
594
|
+
units: {},
|
|
595
|
+
geometry_bbox: { global_id: '1AQAupaRP1txwK1AGiN61V' },
|
|
596
|
+
geometry_volume: { global_id: '1AQAupaRP1txwK1AGiN61V' },
|
|
597
|
+
geometry_area: { global_id: '1AQAupaRP1txwK1AGiN61V' },
|
|
598
|
+
model_audit: {},
|
|
599
|
+
ids_validate: { ids_path: './specs/lod350-walls.ids' },
|
|
600
|
+
ids_explain: { ids_path: './specs/lod350-walls.ids' },
|
|
601
|
+
entity_set_property: { global_id: '1AQAupaRP1txwK1AGiN61V', pset: 'Pset_WallCommon', name: 'FireRating', value: 'EI60' },
|
|
602
|
+
entity_delete_property: { global_id: '1AQAupaRP1txwK1AGiN61V', pset: 'Pset_WallCommon', name: 'FireRating' },
|
|
603
|
+
entity_set_attribute: { global_id: '1AQAupaRP1txwK1AGiN61V', attribute: 'Description', value: 'Touched by MCP suite' },
|
|
604
|
+
entity_create: { type: 'IfcBuildingElementProxy', attributes: [] },
|
|
605
|
+
entity_delete: { express_id: 981 },
|
|
606
|
+
mutation_batch: { operations: [{ tool: 'entity_set_property', args: { global_id: '...', pset: 'Pset_WallCommon', name: 'FireRating', value: 'EI60' } }] },
|
|
607
|
+
mutation_diff: {},
|
|
608
|
+
mutation_undo: { n: 1 },
|
|
609
|
+
model_save: { file_path: '/tmp/audited.ifc' },
|
|
610
|
+
bcf_topic_list: {},
|
|
611
|
+
bcf_topic_create: { title: 'Missing fire rating', priority: 'Major' },
|
|
612
|
+
bcf_topic_update: { guid: '7e87d7f4-...', comment: 'Confirmed with the structural team.' },
|
|
613
|
+
bcf_topic_close: { guid: '7e87d7f4-...' },
|
|
614
|
+
bcf_viewpoint_create: { guid: '7e87d7f4-...', selection_global_ids: ['1AQAupaRP1txwK1AGiN61V'] },
|
|
615
|
+
bcf_export: { file_path: '/tmp/issues.bcfzip' },
|
|
616
|
+
bsdd_search: { query: 'wall' },
|
|
617
|
+
bsdd_class: { ifc_type: 'IfcWall' },
|
|
618
|
+
bsdd_property_sets: { ifc_type: 'IfcWall' },
|
|
619
|
+
bsdd_match: { global_id: '1AQAupaRP1txwK1AGiN61V' },
|
|
620
|
+
model_diff: { a: 'arch_v3', b: 'arch_v4' },
|
|
621
|
+
quantity_diff: { a: 'arch_v3', b: 'arch_v4', type: 'IfcWall', quantity: 'Volume' },
|
|
622
|
+
export_ifc: { file_path: '/tmp/audited.ifc' },
|
|
623
|
+
export_csv: { file_path: '/tmp/walls.csv', type: 'IfcWall', columns: ['GlobalId', 'Name', 'Pset_WallCommon.IsExternal'] },
|
|
624
|
+
export_json: { file_path: '/tmp/walls.json', type: 'IfcWall' },
|
|
625
|
+
export_glb: { file_path: '/tmp/scene.glb' },
|
|
626
|
+
export_ifcx: { file_path: '/tmp/scene.ifcx' },
|
|
627
|
+
export_pdf_report: { file_path: '/tmp/audit.pdf' },
|
|
628
|
+
viewer_ask: { reason: 'to highlight failing fire-rated doors' },
|
|
629
|
+
viewer_open: {},
|
|
630
|
+
viewer_close: {},
|
|
631
|
+
viewer_status: {},
|
|
632
|
+
viewer_colorize: { type: 'IfcWall', color: '#d6ff3f' },
|
|
633
|
+
viewer_isolate: { type: 'IfcWall' },
|
|
634
|
+
viewer_hide: { global_ids: ['1AQAupaRP1txwK1AGiN61V'] },
|
|
635
|
+
viewer_show: { global_ids: ['1AQAupaRP1txwK1AGiN61V'] },
|
|
636
|
+
viewer_reset: {},
|
|
637
|
+
viewer_fly_to: { global_ids: ['1AQAupaRP1txwK1AGiN61V'] },
|
|
638
|
+
viewer_set_section: { axis: 'z', position: 1.5 },
|
|
639
|
+
viewer_clear_section: {},
|
|
640
|
+
viewer_color_by_storey: {},
|
|
641
|
+
viewer_color_by_property: { type: 'IfcWall', pset: 'Pset_WallCommon', property: 'IsExternal' },
|
|
642
|
+
viewer_get_selection: { include: ['attributes', 'properties', 'materials'] },
|
|
643
|
+
viewer_describe_selection: {},
|
|
644
|
+
viewer_wait_for_selection: { timeout_ms: 60000 },
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
/** Build the JSON-RPC tools/call envelope for a given tool example. */
|
|
648
|
+
export function exampleCall(tool: CatalogTool): string {
|
|
649
|
+
return JSON.stringify(
|
|
650
|
+
{
|
|
651
|
+
jsonrpc: '2.0',
|
|
652
|
+
id: 1,
|
|
653
|
+
method: 'tools/call',
|
|
654
|
+
params: { name: tool.name, arguments: EXAMPLES[tool.name] ?? {} },
|
|
655
|
+
},
|
|
656
|
+
null,
|
|
657
|
+
2,
|
|
658
|
+
);
|
|
659
|
+
}
|