sceneview-mcp 3.6.2 → 3.6.4
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/README.md +39 -0
- package/dist/analyze-project.js +500 -0
- package/dist/auth.js +84 -0
- package/dist/billing.js +137 -0
- package/dist/convert-platform.js +302 -0
- package/dist/debug-issue.js +2 -2
- package/dist/explain-api.js +245 -0
- package/dist/extra-guides.js +1 -1
- package/dist/generate-animation.js +576 -0
- package/dist/generate-environment.js +483 -0
- package/dist/generate-gesture.js +532 -0
- package/dist/generate-physics.js +570 -0
- package/dist/generate-scene.js +4 -4
- package/dist/generated/llms-txt.js +6 -0
- package/dist/guides.js +8 -8
- package/dist/index.js +54 -1111
- package/dist/migration.js +2 -2
- package/dist/optimize-scene.js +173 -0
- package/dist/platform-setup.js +11 -11
- package/dist/samples.js +64 -64
- package/dist/search-models.js +214 -0
- package/dist/telemetry.js +120 -0
- package/dist/tiers.js +100 -0
- package/dist/tools/definitions.js +467 -0
- package/dist/tools/handler.js +791 -0
- package/dist/tools/index.js +18 -0
- package/dist/tools/types.js +8 -0
- package/dist/validator.js +1 -1
- package/llms.txt +24 -1
- package/package.json +7 -18
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure tool dispatcher for the SceneView MCP server.
|
|
3
|
+
*
|
|
4
|
+
* This module contains the full tool-handling logic extracted from
|
|
5
|
+
* `mcp/src/index.ts`. It has no dependency on the MCP stdio transport — it
|
|
6
|
+
* just takes a tool name + argument bag and returns a `ToolResult`. This
|
|
7
|
+
* lets the same logic be consumed by:
|
|
8
|
+
*
|
|
9
|
+
* 1. The stdio MCP server in `../index.ts` (legacy npm package path).
|
|
10
|
+
* 2. The HTTP gateway in `mcp-gateway/src/mcp/registry.ts` (hosted path).
|
|
11
|
+
*
|
|
12
|
+
* Zero runtime behaviour change from 3.6.2: every tool returns exactly the
|
|
13
|
+
* same content it did when the logic lived inside the stdio server's
|
|
14
|
+
* `CallToolRequestSchema` handler.
|
|
15
|
+
*
|
|
16
|
+
* Filesystem note: we embed `llms.txt` via `../generated/llms-txt.ts` so
|
|
17
|
+
* this module is safe to run in environments without a filesystem
|
|
18
|
+
* (Cloudflare Workers). The generator script at
|
|
19
|
+
* `mcp/scripts/generate-llms-txt.js` keeps that file in sync with the
|
|
20
|
+
* canonical `llms.txt` at the repo root.
|
|
21
|
+
*/
|
|
22
|
+
import { getSample, SAMPLES } from "../samples.js";
|
|
23
|
+
import { validateCode, formatValidationReport } from "../validator.js";
|
|
24
|
+
import { MIGRATION_GUIDE } from "../migration.js";
|
|
25
|
+
import { parseNodeSections, findNodeSection, listNodeTypes } from "../node-reference.js";
|
|
26
|
+
import { PLATFORM_ROADMAP, BEST_PRACTICES, AR_SETUP_GUIDE, TROUBLESHOOTING_GUIDE, } from "../guides.js";
|
|
27
|
+
import { buildPreviewUrl, validatePreviewInput, formatPreviewResponse, } from "../preview.js";
|
|
28
|
+
import { validateArtifactInput, generateArtifact, formatArtifactResponse, } from "../artifact.js";
|
|
29
|
+
import { getPlatformSetup, } from "../platform-setup.js";
|
|
30
|
+
import { migrateCode, formatMigrationResult } from "../migrate-code.js";
|
|
31
|
+
import { getDebugGuide, autoDetectIssue, DEBUG_CATEGORIES, } from "../debug-issue.js";
|
|
32
|
+
import { generateScene, formatGeneratedScene } from "../generate-scene.js";
|
|
33
|
+
import { ANIMATION_GUIDE, GESTURE_GUIDE, PERFORMANCE_TIPS, } from "../advanced-guides.js";
|
|
34
|
+
import { MATERIAL_GUIDE, COLLISION_GUIDE, MODEL_OPTIMIZATION_GUIDE, WEB_RENDERING_GUIDE, } from "../extra-guides.js";
|
|
35
|
+
import { searchModels, formatSearchResults } from "../search-models.js";
|
|
36
|
+
import { analyzeProject, formatAnalysisReport } from "../analyze-project.js";
|
|
37
|
+
import { LLMS_TXT } from "../generated/llms-txt.js";
|
|
38
|
+
// ─── Legal disclaimer (identical to index.ts 3.6.2) ──────────────────────────
|
|
39
|
+
const DISCLAIMER = "\n\n---\n*Generated code suggestion. Review before use in production. See [TERMS.md](https://github.com/sceneview/sceneview/blob/main/mcp/TERMS.md).*";
|
|
40
|
+
// ─── Sponsor CTA (shown every N tool calls, opt-out via env var) ────────────
|
|
41
|
+
//
|
|
42
|
+
// Non-intrusive monetization: append a one-line sponsor call-to-action after
|
|
43
|
+
// every `SPONSOR_CTA_INTERVAL` tool calls. Disabled by setting
|
|
44
|
+
// `SCENEVIEW_SPONSOR_CTA=0`. The counter is module-scoped (per MCP process
|
|
45
|
+
// lifetime), not persisted.
|
|
46
|
+
const SPONSOR_CTA_INTERVAL = 10;
|
|
47
|
+
const SPONSOR_CTA = "\n\n💙 *Building SceneView is a one-dev labor of love. If this tool saved you time, consider [sponsoring on GitHub](https://github.com/sponsors/sceneview) — it keeps the project alive.*";
|
|
48
|
+
let toolCallCount = 0;
|
|
49
|
+
/** Test-only: reset the module-scoped counter between test cases. */
|
|
50
|
+
export function __resetSponsorCounter() {
|
|
51
|
+
toolCallCount = 0;
|
|
52
|
+
}
|
|
53
|
+
function shouldShowSponsorCta() {
|
|
54
|
+
if (process.env.SCENEVIEW_SPONSOR_CTA === "0")
|
|
55
|
+
return false;
|
|
56
|
+
toolCallCount += 1;
|
|
57
|
+
return toolCallCount % SPONSOR_CTA_INTERVAL === 0;
|
|
58
|
+
}
|
|
59
|
+
function withDisclaimer(content) {
|
|
60
|
+
if (content.length === 0)
|
|
61
|
+
return content;
|
|
62
|
+
const last = content[content.length - 1];
|
|
63
|
+
const suffix = DISCLAIMER + (shouldShowSponsorCta() ? SPONSOR_CTA : "");
|
|
64
|
+
return [
|
|
65
|
+
...content.slice(0, -1),
|
|
66
|
+
{ ...last, text: last.text + suffix },
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
// ─── llms.txt-derived state ──────────────────────────────────────────────────
|
|
70
|
+
/** Raw API docs markdown — exported for the `sceneview://api` resource. */
|
|
71
|
+
export const API_DOCS = LLMS_TXT;
|
|
72
|
+
/** Parsed node-reference sections, shared across `get_node_reference` calls. */
|
|
73
|
+
const NODE_SECTIONS = parseNodeSections(API_DOCS);
|
|
74
|
+
// ─── Public dispatch entrypoint ──────────────────────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* Run a SceneView MCP tool and return its MCP-formatted result.
|
|
77
|
+
*
|
|
78
|
+
* The caller is responsible for:
|
|
79
|
+
* - enforcing tier/pro access (the stdio server does this; the gateway
|
|
80
|
+
* does the same before reaching this function),
|
|
81
|
+
* - rate limiting,
|
|
82
|
+
* - recording billing usage.
|
|
83
|
+
*
|
|
84
|
+
* This function performs NO auth and NO billing — it is pure business logic.
|
|
85
|
+
*
|
|
86
|
+
* @param toolName MCP tool name, e.g. `"get_sample"`.
|
|
87
|
+
* @param args Arguments bag from the MCP client. `undefined` and
|
|
88
|
+
* `null` are both tolerated.
|
|
89
|
+
* @param _ctx Optional context (user, tier, request id). Currently
|
|
90
|
+
* unused by any handler, reserved for future tool wiring.
|
|
91
|
+
*/
|
|
92
|
+
export async function dispatchTool(toolName, args, _ctx = {}) {
|
|
93
|
+
switch (toolName) {
|
|
94
|
+
// ── get_sample ────────────────────────────────────────────────────────────
|
|
95
|
+
case "get_sample": {
|
|
96
|
+
const scenario = args?.scenario;
|
|
97
|
+
const sample = getSample(scenario);
|
|
98
|
+
if (!sample) {
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: `Unknown scenario "${scenario}". Call \`list_samples\` to see available options.`,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
isError: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const isIos = sample.language === "swift";
|
|
110
|
+
const depBlock = isIos
|
|
111
|
+
? [
|
|
112
|
+
`**SPM dependency:**`,
|
|
113
|
+
`\`\`\`swift`,
|
|
114
|
+
`.package(url: "${sample.spmDependency ?? sample.dependency}", from: "3.6.2")`,
|
|
115
|
+
`\`\`\``,
|
|
116
|
+
]
|
|
117
|
+
: [
|
|
118
|
+
`**Gradle dependency:**`,
|
|
119
|
+
`\`\`\`kotlin`,
|
|
120
|
+
`implementation("${sample.dependency}")`,
|
|
121
|
+
`\`\`\``,
|
|
122
|
+
];
|
|
123
|
+
const codeLang = isIos ? "swift" : "kotlin";
|
|
124
|
+
const codeLabel = isIos ? "**Swift (SwiftUI):**" : "**Kotlin (Jetpack Compose):**";
|
|
125
|
+
return {
|
|
126
|
+
content: withDisclaimer([
|
|
127
|
+
{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: [
|
|
130
|
+
`## ${sample.title}`,
|
|
131
|
+
``,
|
|
132
|
+
`**Tags:** ${sample.tags.join(", ")}`,
|
|
133
|
+
``,
|
|
134
|
+
...depBlock,
|
|
135
|
+
``,
|
|
136
|
+
codeLabel,
|
|
137
|
+
`\`\`\`${codeLang}`,
|
|
138
|
+
sample.code,
|
|
139
|
+
`\`\`\``,
|
|
140
|
+
``,
|
|
141
|
+
`**Prompt that generates this:**`,
|
|
142
|
+
`> ${sample.prompt}`,
|
|
143
|
+
].join("\n"),
|
|
144
|
+
},
|
|
145
|
+
]),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// ── list_samples ──────────────────────────────────────────────────────────
|
|
149
|
+
case "list_samples": {
|
|
150
|
+
const filterTag = args?.tag;
|
|
151
|
+
const entries = Object.values(SAMPLES).filter((s) => !filterTag || s.tags.includes(filterTag));
|
|
152
|
+
if (entries.length === 0) {
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: "text",
|
|
157
|
+
text: `No samples found with tag "${filterTag}". Available tags: 3d, ar, model, geometry, animation, camera, environment, anchor, plane-detection, image-tracking, cloud-anchor, point-cloud, placement, gestures, physics, sky, fog, lines, text, reflection, post-processing, ios, swift, video, lighting`,
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const header = filterTag
|
|
163
|
+
? `## SceneView samples tagged \`${filterTag}\` (${entries.length})\n`
|
|
164
|
+
: `## All SceneView samples (${entries.length})\n`;
|
|
165
|
+
const rows = entries
|
|
166
|
+
.map((s) => {
|
|
167
|
+
const depLabel = s.language === "swift" ? "*SPM:*" : "*Dependency:*";
|
|
168
|
+
return `### \`${s.id}\`\n**${s.title}**${s.language === "swift" ? " (Swift/iOS)" : ""}\n${s.description}\n*Tags:* ${s.tags.join(", ")}\n${depLabel} \`${s.dependency}\`\n\nCall \`get_sample("${s.id}")\` for the full code.`;
|
|
169
|
+
})
|
|
170
|
+
.join("\n\n---\n\n");
|
|
171
|
+
return { content: withDisclaimer([{ type: "text", text: header + rows }]) };
|
|
172
|
+
}
|
|
173
|
+
// ── get_setup ─────────────────────────────────────────────────────────────
|
|
174
|
+
case "get_setup": {
|
|
175
|
+
const type = args?.type;
|
|
176
|
+
if (type === "3d") {
|
|
177
|
+
return {
|
|
178
|
+
content: withDisclaimer([
|
|
179
|
+
{
|
|
180
|
+
type: "text",
|
|
181
|
+
text: [
|
|
182
|
+
`## SceneView — 3D setup`,
|
|
183
|
+
``,
|
|
184
|
+
`### build.gradle.kts`,
|
|
185
|
+
`\`\`\`kotlin`,
|
|
186
|
+
`dependencies {`,
|
|
187
|
+
` implementation("io.github.sceneview:sceneview:3.6.2")`,
|
|
188
|
+
`}`,
|
|
189
|
+
`\`\`\``,
|
|
190
|
+
``,
|
|
191
|
+
`No manifest changes required for 3D-only scenes.`,
|
|
192
|
+
].join("\n"),
|
|
193
|
+
},
|
|
194
|
+
]),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (type === "ar") {
|
|
198
|
+
return {
|
|
199
|
+
content: withDisclaimer([
|
|
200
|
+
{
|
|
201
|
+
type: "text",
|
|
202
|
+
text: [
|
|
203
|
+
`## SceneView — AR setup`,
|
|
204
|
+
``,
|
|
205
|
+
`### build.gradle.kts`,
|
|
206
|
+
`\`\`\`kotlin`,
|
|
207
|
+
`dependencies {`,
|
|
208
|
+
` implementation("io.github.sceneview:arsceneview:3.6.2")`,
|
|
209
|
+
`}`,
|
|
210
|
+
`\`\`\``,
|
|
211
|
+
``,
|
|
212
|
+
`### AndroidManifest.xml`,
|
|
213
|
+
`\`\`\`xml`,
|
|
214
|
+
`<uses-permission android:name="android.permission.CAMERA" />`,
|
|
215
|
+
`<uses-feature android:name="android.hardware.camera.ar" android:required="true" />`,
|
|
216
|
+
`<application>`,
|
|
217
|
+
` <meta-data android:name="com.google.ar.core" android:value="required" />`,
|
|
218
|
+
`</application>`,
|
|
219
|
+
`\`\`\``,
|
|
220
|
+
].join("\n"),
|
|
221
|
+
},
|
|
222
|
+
]),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
content: [{ type: "text", text: `Unknown type "${type}". Use "3d" or "ar".` }],
|
|
227
|
+
isError: true,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
// ── validate_code ─────────────────────────────────────────────────────────
|
|
231
|
+
case "validate_code": {
|
|
232
|
+
const code = args?.code;
|
|
233
|
+
if (!code || typeof code !== "string") {
|
|
234
|
+
return {
|
|
235
|
+
content: [{ type: "text", text: "Missing required parameter: `code`" }],
|
|
236
|
+
isError: true,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const issues = validateCode(code);
|
|
240
|
+
const report = formatValidationReport(issues);
|
|
241
|
+
return { content: withDisclaimer([{ type: "text", text: report }]) };
|
|
242
|
+
}
|
|
243
|
+
// ── get_migration_guide ───────────────────────────────────────────────────
|
|
244
|
+
case "get_migration_guide": {
|
|
245
|
+
return { content: withDisclaimer([{ type: "text", text: MIGRATION_GUIDE }]) };
|
|
246
|
+
}
|
|
247
|
+
// ── get_node_reference ────────────────────────────────────────────────────
|
|
248
|
+
case "get_node_reference": {
|
|
249
|
+
const nodeType = args?.nodeType;
|
|
250
|
+
if (!nodeType || typeof nodeType !== "string") {
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: "text", text: "Missing required parameter: `nodeType`" }],
|
|
253
|
+
isError: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const section = findNodeSection(NODE_SECTIONS, nodeType);
|
|
257
|
+
if (!section) {
|
|
258
|
+
const available = listNodeTypes(NODE_SECTIONS).join(", ");
|
|
259
|
+
return {
|
|
260
|
+
content: [
|
|
261
|
+
{
|
|
262
|
+
type: "text",
|
|
263
|
+
text: [
|
|
264
|
+
`No reference found for node type \`${nodeType}\`.`,
|
|
265
|
+
``,
|
|
266
|
+
`**Available node types:**`,
|
|
267
|
+
available,
|
|
268
|
+
].join("\n"),
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
content: withDisclaimer([
|
|
275
|
+
{
|
|
276
|
+
type: "text",
|
|
277
|
+
text: [
|
|
278
|
+
`## \`${section.name}\` — API Reference`,
|
|
279
|
+
``,
|
|
280
|
+
section.content,
|
|
281
|
+
].join("\n"),
|
|
282
|
+
},
|
|
283
|
+
]),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
// ── get_platform_roadmap ────────────────────────────────────────────────
|
|
287
|
+
case "get_platform_roadmap": {
|
|
288
|
+
return { content: withDisclaimer([{ type: "text", text: PLATFORM_ROADMAP }]) };
|
|
289
|
+
}
|
|
290
|
+
// ── get_best_practices ───────────────────────────────────────────────────
|
|
291
|
+
case "get_best_practices": {
|
|
292
|
+
const category = args?.category || "all";
|
|
293
|
+
const text = BEST_PRACTICES[category] ?? BEST_PRACTICES["all"];
|
|
294
|
+
return { content: withDisclaimer([{ type: "text", text }]) };
|
|
295
|
+
}
|
|
296
|
+
// ── get_ar_setup ─────────────────────────────────────────────────────────
|
|
297
|
+
case "get_ar_setup": {
|
|
298
|
+
return { content: withDisclaimer([{ type: "text", text: AR_SETUP_GUIDE }]) };
|
|
299
|
+
}
|
|
300
|
+
// ── get_troubleshooting ──────────────────────────────────────────────────
|
|
301
|
+
case "get_troubleshooting": {
|
|
302
|
+
return { content: withDisclaimer([{ type: "text", text: TROUBLESHOOTING_GUIDE }]) };
|
|
303
|
+
}
|
|
304
|
+
// ── get_ios_setup ─────────────────────────────────────────────────────────
|
|
305
|
+
case "get_ios_setup": {
|
|
306
|
+
const iosType = args?.type;
|
|
307
|
+
if (iosType === "3d") {
|
|
308
|
+
return {
|
|
309
|
+
content: withDisclaimer([
|
|
310
|
+
{
|
|
311
|
+
type: "text",
|
|
312
|
+
text: [
|
|
313
|
+
`## SceneViewSwift — iOS/macOS/visionOS 3D Setup`,
|
|
314
|
+
``,
|
|
315
|
+
`### 1. Add SPM Dependency`,
|
|
316
|
+
``,
|
|
317
|
+
`In Xcode: **File → Add Package Dependencies** → paste:`,
|
|
318
|
+
`\`\`\``,
|
|
319
|
+
`https://github.com/sceneview/sceneview`,
|
|
320
|
+
`\`\`\``,
|
|
321
|
+
`Set version rule to **"from: 3.6.2"**.`,
|
|
322
|
+
``,
|
|
323
|
+
`Or in Package.swift:`,
|
|
324
|
+
`\`\`\`swift`,
|
|
325
|
+
`// swift-tools-version: 5.10`,
|
|
326
|
+
`import PackageDescription`,
|
|
327
|
+
``,
|
|
328
|
+
`let package = Package(`,
|
|
329
|
+
` name: "MyApp",`,
|
|
330
|
+
` platforms: [.iOS(.v17), .macOS(.v14), .visionOS(.v1)],`,
|
|
331
|
+
` dependencies: [`,
|
|
332
|
+
` .package(url: "https://github.com/sceneview/sceneview", from: "3.6.2")`,
|
|
333
|
+
` ],`,
|
|
334
|
+
` targets: [`,
|
|
335
|
+
` .executableTarget(`,
|
|
336
|
+
` name: "MyApp",`,
|
|
337
|
+
` dependencies: [`,
|
|
338
|
+
` .product(name: "SceneViewSwift", package: "sceneview")`,
|
|
339
|
+
` ]`,
|
|
340
|
+
` )`,
|
|
341
|
+
` ]`,
|
|
342
|
+
`)`,
|
|
343
|
+
`\`\`\``,
|
|
344
|
+
``,
|
|
345
|
+
`### 2. Minimum Platform Versions`,
|
|
346
|
+
``,
|
|
347
|
+
`| Platform | Minimum Version |`,
|
|
348
|
+
`|----------|-----------------|`,
|
|
349
|
+
`| iOS | 17.0 |`,
|
|
350
|
+
`| macOS | 14.0 |`,
|
|
351
|
+
`| visionOS | 1.0 |`,
|
|
352
|
+
``,
|
|
353
|
+
`### 3. Basic SwiftUI Integration`,
|
|
354
|
+
``,
|
|
355
|
+
`\`\`\`swift`,
|
|
356
|
+
`import SwiftUI`,
|
|
357
|
+
`import SceneViewSwift`,
|
|
358
|
+
`import RealityKit`,
|
|
359
|
+
``,
|
|
360
|
+
`struct ContentView: View {`,
|
|
361
|
+
` @State private var model: ModelNode?`,
|
|
362
|
+
``,
|
|
363
|
+
` var body: some View {`,
|
|
364
|
+
` SceneView { root in`,
|
|
365
|
+
` if let model {`,
|
|
366
|
+
` root.addChild(model.entity)`,
|
|
367
|
+
` }`,
|
|
368
|
+
` }`,
|
|
369
|
+
` .cameraControls(.orbit)`,
|
|
370
|
+
` .task {`,
|
|
371
|
+
` model = try? await ModelNode.load("models/car.usdz")`,
|
|
372
|
+
` model?.scaleToUnits(1.0)`,
|
|
373
|
+
` }`,
|
|
374
|
+
` }`,
|
|
375
|
+
`}`,
|
|
376
|
+
`\`\`\``,
|
|
377
|
+
``,
|
|
378
|
+
`### 4. Model Formats`,
|
|
379
|
+
``,
|
|
380
|
+
`| Format | Support |`,
|
|
381
|
+
`|--------|---------|`,
|
|
382
|
+
`| USDZ | Native — recommended for iOS |`,
|
|
383
|
+
`| .reality | Native — RealityKit format |`,
|
|
384
|
+
`| glTF/GLB | Planned via GLTFKit2 |`,
|
|
385
|
+
``,
|
|
386
|
+
`No manifest or permission changes needed for 3D-only scenes.`,
|
|
387
|
+
].join("\n"),
|
|
388
|
+
},
|
|
389
|
+
]),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (iosType === "ar") {
|
|
393
|
+
return {
|
|
394
|
+
content: withDisclaimer([
|
|
395
|
+
{
|
|
396
|
+
type: "text",
|
|
397
|
+
text: [
|
|
398
|
+
`## SceneViewSwift — iOS AR Setup`,
|
|
399
|
+
``,
|
|
400
|
+
`### 1. Add SPM Dependency`,
|
|
401
|
+
``,
|
|
402
|
+
`\`\`\`swift`,
|
|
403
|
+
`.package(url: "https://github.com/sceneview/sceneview", from: "3.6.2")`,
|
|
404
|
+
`\`\`\``,
|
|
405
|
+
``,
|
|
406
|
+
`### 2. Minimum Platform`,
|
|
407
|
+
``,
|
|
408
|
+
`AR requires **iOS 17.0+** (ARKit + RealityKit). macOS and visionOS use different AR APIs.`,
|
|
409
|
+
``,
|
|
410
|
+
`### 3. Info.plist — Camera Permission`,
|
|
411
|
+
``,
|
|
412
|
+
`Add to your Info.plist (required for AR camera access):`,
|
|
413
|
+
`\`\`\`xml`,
|
|
414
|
+
`<key>NSCameraUsageDescription</key>`,
|
|
415
|
+
`<string>This app uses the camera for augmented reality.</string>`,
|
|
416
|
+
`\`\`\``,
|
|
417
|
+
``,
|
|
418
|
+
`### 4. Basic AR Integration`,
|
|
419
|
+
``,
|
|
420
|
+
`\`\`\`swift`,
|
|
421
|
+
`import SwiftUI`,
|
|
422
|
+
`import SceneViewSwift`,
|
|
423
|
+
`import RealityKit`,
|
|
424
|
+
``,
|
|
425
|
+
`struct ARContentView: View {`,
|
|
426
|
+
` @State private var model: ModelNode?`,
|
|
427
|
+
``,
|
|
428
|
+
` var body: some View {`,
|
|
429
|
+
` ARSceneView(`,
|
|
430
|
+
` planeDetection: .horizontal,`,
|
|
431
|
+
` showCoachingOverlay: true,`,
|
|
432
|
+
` onTapOnPlane: { position, arView in`,
|
|
433
|
+
` guard let model else { return }`,
|
|
434
|
+
` let anchor = AnchorNode.world(position: position)`,
|
|
435
|
+
` let clone = model.entity.clone(recursive: true)`,
|
|
436
|
+
` clone.scale = .init(repeating: 0.3)`,
|
|
437
|
+
` anchor.add(clone)`,
|
|
438
|
+
` arView.scene.addAnchor(anchor.entity)`,
|
|
439
|
+
` }`,
|
|
440
|
+
` )`,
|
|
441
|
+
` .edgesIgnoringSafeArea(.all)`,
|
|
442
|
+
` .task {`,
|
|
443
|
+
` model = try? await ModelNode.load("models/robot.usdz")`,
|
|
444
|
+
` }`,
|
|
445
|
+
` }`,
|
|
446
|
+
`}`,
|
|
447
|
+
`\`\`\``,
|
|
448
|
+
``,
|
|
449
|
+
`### 5. AR Configuration Options`,
|
|
450
|
+
``,
|
|
451
|
+
`| Parameter | Options | Default |`,
|
|
452
|
+
`|-----------|---------|---------|`,
|
|
453
|
+
`| \`planeDetection\` | \`.none\`, \`.horizontal\`, \`.vertical\`, \`.both\` | \`.horizontal\` |`,
|
|
454
|
+
`| \`showPlaneOverlay\` | \`true\` / \`false\` | \`true\` |`,
|
|
455
|
+
`| \`showCoachingOverlay\` | \`true\` / \`false\` | \`true\` |`,
|
|
456
|
+
`| \`imageTrackingDatabase\` | \`Set<ARReferenceImage>\` | \`nil\` |`,
|
|
457
|
+
``,
|
|
458
|
+
`### 6. Image Tracking`,
|
|
459
|
+
``,
|
|
460
|
+
`\`\`\`swift`,
|
|
461
|
+
`let images = AugmentedImageNode.createImageDatabase([`,
|
|
462
|
+
` AugmentedImageNode.ReferenceImage(`,
|
|
463
|
+
` name: "poster",`,
|
|
464
|
+
` image: UIImage(named: "poster_ref")!,`,
|
|
465
|
+
` physicalWidth: 0.3 // meters`,
|
|
466
|
+
` )`,
|
|
467
|
+
`])`,
|
|
468
|
+
``,
|
|
469
|
+
`ARSceneView(`,
|
|
470
|
+
` imageTrackingDatabase: images,`,
|
|
471
|
+
` onImageDetected: { name, anchor, arView in`,
|
|
472
|
+
` let cube = GeometryNode.cube(size: 0.1, color: .blue)`,
|
|
473
|
+
` anchor.add(cube.entity)`,
|
|
474
|
+
` arView.scene.addAnchor(anchor.entity)`,
|
|
475
|
+
` }`,
|
|
476
|
+
`)`,
|
|
477
|
+
`\`\`\``,
|
|
478
|
+
].join("\n"),
|
|
479
|
+
},
|
|
480
|
+
]),
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
content: [{ type: "text", text: `Unknown type "${iosType}". Use "3d" or "ar".` }],
|
|
485
|
+
isError: true,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
// ── get_web_setup ────────────────────────────────────────────────────────
|
|
489
|
+
case "get_web_setup": {
|
|
490
|
+
return {
|
|
491
|
+
content: withDisclaimer([
|
|
492
|
+
{
|
|
493
|
+
type: "text",
|
|
494
|
+
text: [
|
|
495
|
+
`## SceneView Web — Browser 3D Setup`,
|
|
496
|
+
``,
|
|
497
|
+
`SceneView Web uses **Filament.js** — the same rendering engine as Android, compiled to WebAssembly (WebGL2).`,
|
|
498
|
+
``,
|
|
499
|
+
`### 1. Install`,
|
|
500
|
+
``,
|
|
501
|
+
`\`\`\`bash`,
|
|
502
|
+
`npm install @sceneview/sceneview-web`,
|
|
503
|
+
`\`\`\``,
|
|
504
|
+
``,
|
|
505
|
+
`Or in a Kotlin/JS Gradle project:`,
|
|
506
|
+
`\`\`\`kotlin`,
|
|
507
|
+
`// build.gradle.kts`,
|
|
508
|
+
`kotlin {`,
|
|
509
|
+
` js(IR) { browser(); binaries.executable() }`,
|
|
510
|
+
` sourceSets {`,
|
|
511
|
+
` jsMain.dependencies {`,
|
|
512
|
+
` implementation("@sceneview/sceneview-web")`,
|
|
513
|
+
` // or: implementation(project(":sceneview-web"))`,
|
|
514
|
+
` }`,
|
|
515
|
+
` }`,
|
|
516
|
+
`}`,
|
|
517
|
+
`\`\`\``,
|
|
518
|
+
``,
|
|
519
|
+
`### 2. HTML`,
|
|
520
|
+
``,
|
|
521
|
+
`\`\`\`html`,
|
|
522
|
+
`<canvas id="scene-canvas" style="width:100%;height:100vh"></canvas>`,
|
|
523
|
+
`<script src="your-app.js"></script>`,
|
|
524
|
+
`\`\`\``,
|
|
525
|
+
``,
|
|
526
|
+
`### 3. Kotlin/JS Code`,
|
|
527
|
+
``,
|
|
528
|
+
`\`\`\`kotlin`,
|
|
529
|
+
`import io.github.sceneview.web.SceneView`,
|
|
530
|
+
`import kotlinx.browser.document`,
|
|
531
|
+
`import org.w3c.dom.HTMLCanvasElement`,
|
|
532
|
+
``,
|
|
533
|
+
`fun main() {`,
|
|
534
|
+
` val canvas = document.getElementById("scene-canvas") as HTMLCanvasElement`,
|
|
535
|
+
` canvas.width = canvas.clientWidth`,
|
|
536
|
+
` canvas.height = canvas.clientHeight`,
|
|
537
|
+
``,
|
|
538
|
+
` SceneView.create(`,
|
|
539
|
+
` canvas = canvas,`,
|
|
540
|
+
` configure = {`,
|
|
541
|
+
` camera {`,
|
|
542
|
+
` eye(0.0, 1.5, 5.0)`,
|
|
543
|
+
` target(0.0, 0.0, 0.0)`,
|
|
544
|
+
` fov(45.0)`,
|
|
545
|
+
` }`,
|
|
546
|
+
` light {`,
|
|
547
|
+
` directional()`,
|
|
548
|
+
` intensity(100_000.0)`,
|
|
549
|
+
` }`,
|
|
550
|
+
` model("models/DamagedHelmet.glb")`,
|
|
551
|
+
` autoRotate()`,
|
|
552
|
+
` },`,
|
|
553
|
+
` onReady = { sceneView ->`,
|
|
554
|
+
` sceneView.startRendering()`,
|
|
555
|
+
` }`,
|
|
556
|
+
` )`,
|
|
557
|
+
`}`,
|
|
558
|
+
`\`\`\``,
|
|
559
|
+
``,
|
|
560
|
+
`### 4. Features`,
|
|
561
|
+
``,
|
|
562
|
+
`- Same Filament PBR renderer as Android (WASM)`,
|
|
563
|
+
`- glTF 2.0 / GLB model loading`,
|
|
564
|
+
`- IBL environment lighting (KTX)`,
|
|
565
|
+
`- Orbit camera with mouse/touch/pinch controls`,
|
|
566
|
+
`- Auto-rotation`,
|
|
567
|
+
`- Directional, point, and spot lights`,
|
|
568
|
+
``,
|
|
569
|
+
`### 5. Limitations`,
|
|
570
|
+
``,
|
|
571
|
+
`- No AR (requires native sensors)`,
|
|
572
|
+
`- WebGL2 required (~95% of browsers)`,
|
|
573
|
+
`- glTF/GLB format only (same as Android)`,
|
|
574
|
+
].join("\n"),
|
|
575
|
+
},
|
|
576
|
+
]),
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
// ── render_3d_preview ──────────────────────────────────────────────────
|
|
580
|
+
case "render_3d_preview": {
|
|
581
|
+
const modelUrl = args?.modelUrl;
|
|
582
|
+
const codeSnippet = args?.codeSnippet;
|
|
583
|
+
const autoRotate = args?.autoRotate;
|
|
584
|
+
const ar = args?.ar;
|
|
585
|
+
const title = args?.title;
|
|
586
|
+
const validationError = validatePreviewInput(modelUrl, codeSnippet);
|
|
587
|
+
if (validationError) {
|
|
588
|
+
return {
|
|
589
|
+
content: [{ type: "text", text: `Error: ${validationError}` }],
|
|
590
|
+
isError: true,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
const result = buildPreviewUrl({ modelUrl, codeSnippet, autoRotate, ar, title });
|
|
594
|
+
const text = formatPreviewResponse(result);
|
|
595
|
+
return { content: withDisclaimer([{ type: "text", text }]) };
|
|
596
|
+
}
|
|
597
|
+
// ── create_3d_artifact ───────────────────────────────────────────────────
|
|
598
|
+
case "create_3d_artifact": {
|
|
599
|
+
const artifactInput = {
|
|
600
|
+
type: args?.type,
|
|
601
|
+
modelUrl: args?.modelUrl,
|
|
602
|
+
title: args?.title,
|
|
603
|
+
data: args?.data,
|
|
604
|
+
options: args?.options,
|
|
605
|
+
hotspots: args?.hotspots,
|
|
606
|
+
shapes: args?.shapes,
|
|
607
|
+
};
|
|
608
|
+
const validationError = validateArtifactInput(artifactInput);
|
|
609
|
+
if (validationError) {
|
|
610
|
+
return {
|
|
611
|
+
content: [{ type: "text", text: `Error: ${validationError}` }],
|
|
612
|
+
isError: true,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
const result = generateArtifact(artifactInput);
|
|
616
|
+
const text = formatArtifactResponse(result);
|
|
617
|
+
return { content: withDisclaimer([{ type: "text", text }]) };
|
|
618
|
+
}
|
|
619
|
+
// ── get_platform_setup ─────────────────────────────────────────────────
|
|
620
|
+
case "get_platform_setup": {
|
|
621
|
+
const platform = args?.platform;
|
|
622
|
+
const setupType = args?.type;
|
|
623
|
+
if (!platform || !setupType) {
|
|
624
|
+
return {
|
|
625
|
+
content: [{ type: "text", text: "Missing required parameters: `platform` and `type`." }],
|
|
626
|
+
isError: true,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
const guide = getPlatformSetup(platform, setupType);
|
|
630
|
+
return { content: withDisclaimer([{ type: "text", text: guide }]) };
|
|
631
|
+
}
|
|
632
|
+
// ── migrate_code ─────────────────────────────────────────────────────────
|
|
633
|
+
case "migrate_code": {
|
|
634
|
+
const code = args?.code;
|
|
635
|
+
if (!code || typeof code !== "string") {
|
|
636
|
+
return {
|
|
637
|
+
content: [{ type: "text", text: "Missing required parameter: `code`." }],
|
|
638
|
+
isError: true,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
const migrationResult = migrateCode(code);
|
|
642
|
+
const migrationReport = formatMigrationResult(migrationResult);
|
|
643
|
+
return { content: withDisclaimer([{ type: "text", text: migrationReport }]) };
|
|
644
|
+
}
|
|
645
|
+
// ── debug_issue ──────────────────────────────────────────────────────────
|
|
646
|
+
case "debug_issue": {
|
|
647
|
+
let category = args?.category;
|
|
648
|
+
const desc = args?.description;
|
|
649
|
+
if (!category && desc) {
|
|
650
|
+
category = autoDetectIssue(desc) ?? undefined;
|
|
651
|
+
}
|
|
652
|
+
if (!category) {
|
|
653
|
+
return {
|
|
654
|
+
content: [{
|
|
655
|
+
type: "text",
|
|
656
|
+
text: `Please provide a \`category\` or \`description\`.\n\nAvailable categories: ${DEBUG_CATEGORIES.join(", ")}`,
|
|
657
|
+
}],
|
|
658
|
+
isError: true,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
const debugGuide = getDebugGuide(category);
|
|
662
|
+
const prefix = desc && autoDetectIssue(desc) === category
|
|
663
|
+
? `> Auto-detected category: **${category}** from your description.\n\n`
|
|
664
|
+
: "";
|
|
665
|
+
return { content: withDisclaimer([{ type: "text", text: prefix + debugGuide }]) };
|
|
666
|
+
}
|
|
667
|
+
// ── generate_scene ───────────────────────────────────────────────────────
|
|
668
|
+
case "generate_scene": {
|
|
669
|
+
const sceneDesc = args?.description;
|
|
670
|
+
if (!sceneDesc || typeof sceneDesc !== "string") {
|
|
671
|
+
return {
|
|
672
|
+
content: [{ type: "text", text: "Missing required parameter: `description`." }],
|
|
673
|
+
isError: true,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
const sceneResult = generateScene(sceneDesc);
|
|
677
|
+
const sceneReport = formatGeneratedScene(sceneResult);
|
|
678
|
+
return { content: withDisclaimer([{ type: "text", text: sceneReport }]) };
|
|
679
|
+
}
|
|
680
|
+
// ── list_platforms ────────────────────────────────────────────────────────
|
|
681
|
+
case "list_platforms": {
|
|
682
|
+
const platforms = [
|
|
683
|
+
{ platform: "Android", renderer: "Filament", framework: "Jetpack Compose", status: "Stable", version: "3.6.2", dependency: "io.github.sceneview:sceneview:3.6.2", features: ["3D", "AR (ARCore)", "Model loading (GLB/glTF)", "Geometry nodes", "Physics", "Gestures"] },
|
|
684
|
+
{ platform: "Android TV", renderer: "Filament", framework: "Compose TV", status: "Alpha", version: "3.6.2", dependency: "io.github.sceneview:sceneview:3.6.2", features: ["3D", "D-pad controls", "Auto-rotation", "Model loading"] },
|
|
685
|
+
{ platform: "Android XR", renderer: "Jetpack XR SceneCore", framework: "Compose XR", status: "Planned", version: "-", dependency: "-", features: ["Spatial computing", "Hand tracking", "Passthrough"] },
|
|
686
|
+
{ platform: "iOS", renderer: "RealityKit", framework: "SwiftUI", status: "Alpha", version: "3.6.2", dependency: "SceneViewSwift (SPM)", features: ["3D", "AR (ARKit)", "16 node types", "USDZ models"] },
|
|
687
|
+
{ platform: "macOS", renderer: "RealityKit", framework: "SwiftUI", status: "Alpha", version: "3.6.2", dependency: "SceneViewSwift (SPM)", features: ["3D", "Orbit camera", "USDZ models"] },
|
|
688
|
+
{ platform: "visionOS", renderer: "RealityKit", framework: "SwiftUI", status: "Alpha", version: "3.6.2", dependency: "SceneViewSwift (SPM)", features: ["3D", "Immersive spaces", "Hand tracking (planned)"] },
|
|
689
|
+
{ platform: "Web", renderer: "Filament.js (WASM)", framework: "Kotlin/JS", status: "Alpha", version: "3.6.2", dependency: "@sceneview/sceneview-web", features: ["3D", "WebXR AR/VR", "GLB models", "WebGL2"] },
|
|
690
|
+
{ platform: "Desktop", renderer: "Software / Filament JNI", framework: "Compose Desktop", status: "Alpha", version: "3.6.2", dependency: "sceneview-desktop (local)", features: ["3D", "Software renderer", "Wireframe"] },
|
|
691
|
+
{ platform: "Flutter", renderer: "Filament / RealityKit", framework: "PlatformView", status: "Alpha", version: "3.6.2", dependency: "flutter pub: sceneview", features: ["3D", "AR", "Android + iOS bridge"] },
|
|
692
|
+
];
|
|
693
|
+
const lines = [
|
|
694
|
+
"## SceneView Supported Platforms\n",
|
|
695
|
+
"| Platform | Renderer | Framework | Status | Version |",
|
|
696
|
+
"|----------|----------|-----------|--------|---------|",
|
|
697
|
+
...platforms.map(p => `| ${p.platform} | ${p.renderer} | ${p.framework} | ${p.status} | ${p.version} |`),
|
|
698
|
+
"",
|
|
699
|
+
"### Platform Details\n",
|
|
700
|
+
...platforms.map(p => [
|
|
701
|
+
`**${p.platform}** (${p.status})`,
|
|
702
|
+
`- Renderer: ${p.renderer}`,
|
|
703
|
+
`- Framework: ${p.framework}`,
|
|
704
|
+
`- Dependency: \`${p.dependency}\``,
|
|
705
|
+
`- Features: ${p.features.join(", ")}`,
|
|
706
|
+
"",
|
|
707
|
+
].join("\n")),
|
|
708
|
+
"### Architecture",
|
|
709
|
+
"",
|
|
710
|
+
"SceneView uses **native renderers per platform**: Filament on Android/Web/Desktop, RealityKit on Apple (iOS/macOS/visionOS).",
|
|
711
|
+
"KMP `sceneview-core` shares logic (math, collision, geometry, animations) across all platforms.",
|
|
712
|
+
];
|
|
713
|
+
return { content: withDisclaimer([{ type: "text", text: lines.join("\n") }]) };
|
|
714
|
+
}
|
|
715
|
+
// ── get_animation_guide ─────────────────────────────────────────────────
|
|
716
|
+
case "get_animation_guide": {
|
|
717
|
+
return { content: withDisclaimer([{ type: "text", text: ANIMATION_GUIDE }]) };
|
|
718
|
+
}
|
|
719
|
+
// ── get_gesture_guide ────────────────────────────────────────────────────
|
|
720
|
+
case "get_gesture_guide": {
|
|
721
|
+
return { content: withDisclaimer([{ type: "text", text: GESTURE_GUIDE }]) };
|
|
722
|
+
}
|
|
723
|
+
// ── get_performance_tips ─────────────────────────────────────────────────
|
|
724
|
+
case "get_performance_tips": {
|
|
725
|
+
return { content: withDisclaimer([{ type: "text", text: PERFORMANCE_TIPS }]) };
|
|
726
|
+
}
|
|
727
|
+
// ── get_material_guide ───────────────────────────────────────────────────
|
|
728
|
+
case "get_material_guide": {
|
|
729
|
+
return { content: withDisclaimer([{ type: "text", text: MATERIAL_GUIDE }]) };
|
|
730
|
+
}
|
|
731
|
+
// ── get_collision_guide ──────────────────────────────────────────────────
|
|
732
|
+
case "get_collision_guide": {
|
|
733
|
+
return { content: withDisclaimer([{ type: "text", text: COLLISION_GUIDE }]) };
|
|
734
|
+
}
|
|
735
|
+
// ── get_model_optimization_guide ─────────────────────────────────────────
|
|
736
|
+
case "get_model_optimization_guide": {
|
|
737
|
+
return { content: withDisclaimer([{ type: "text", text: MODEL_OPTIMIZATION_GUIDE }]) };
|
|
738
|
+
}
|
|
739
|
+
// ── get_web_rendering_guide ──────────────────────────────────────────────
|
|
740
|
+
case "get_web_rendering_guide": {
|
|
741
|
+
return { content: withDisclaimer([{ type: "text", text: WEB_RENDERING_GUIDE }]) };
|
|
742
|
+
}
|
|
743
|
+
// ── search_models ────────────────────────────────────────────────────────
|
|
744
|
+
case "search_models": {
|
|
745
|
+
const query = args?.query;
|
|
746
|
+
if (!query || typeof query !== "string" || query.trim().length === 0) {
|
|
747
|
+
return {
|
|
748
|
+
content: [{ type: "text", text: "Missing required parameter: `query` must be a non-empty string." }],
|
|
749
|
+
isError: true,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
const searchResult = await searchModels({
|
|
753
|
+
query,
|
|
754
|
+
category: args?.category,
|
|
755
|
+
downloadable: args?.downloadable,
|
|
756
|
+
maxResults: args?.maxResults,
|
|
757
|
+
});
|
|
758
|
+
const searchText = formatSearchResults(searchResult);
|
|
759
|
+
return {
|
|
760
|
+
content: withDisclaimer([{ type: "text", text: searchText }]),
|
|
761
|
+
isError: searchResult.ok ? undefined : true,
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
// ── analyze_project ───────────────────────────────────────────────────────
|
|
765
|
+
case "analyze_project": {
|
|
766
|
+
const rawPath = args?.path;
|
|
767
|
+
const analyzePath = typeof rawPath === "string" && rawPath.length > 0 ? rawPath : undefined;
|
|
768
|
+
try {
|
|
769
|
+
const analysis = await analyzeProject({ path: analyzePath });
|
|
770
|
+
const report = formatAnalysisReport(analysis);
|
|
771
|
+
return { content: withDisclaimer([{ type: "text", text: report }]) };
|
|
772
|
+
}
|
|
773
|
+
catch (err) {
|
|
774
|
+
return {
|
|
775
|
+
content: [
|
|
776
|
+
{
|
|
777
|
+
type: "text",
|
|
778
|
+
text: `analyze_project failed: ${err.message}`,
|
|
779
|
+
},
|
|
780
|
+
],
|
|
781
|
+
isError: true,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
default:
|
|
786
|
+
return {
|
|
787
|
+
content: [{ type: "text", text: `Unknown tool: ${toolName}` }],
|
|
788
|
+
isError: true,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
}
|