sceneview-mcp 4.0.12 → 4.0.13
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 +4 -2
- package/dist/android-docs.js +206 -0
- package/dist/examples.js +136 -0
- package/dist/generated/llms-txt.js +1 -1
- package/dist/generated/version.js +2 -2
- package/dist/guides.js +1 -1
- package/dist/index.js +29 -0
- package/dist/issues.js +49 -2
- package/dist/telemetry.js +178 -2
- package/dist/tiers.js +2 -0
- package/dist/tools/definitions.js +38 -0
- package/dist/tools/handler.js +33 -0
- package/package.json +5 -2
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
// build time so the MCP server, telemetry, and the install-snippet
|
|
5
5
|
// generators all report the actually-published versions instead of
|
|
6
6
|
// stale hardcoded constants. See #941.
|
|
7
|
-
export const PACKAGE_VERSION = "4.0.
|
|
8
|
-
export const LATEST_SCENEVIEW_RELEASE = "4.
|
|
7
|
+
export const PACKAGE_VERSION = "4.0.13";
|
|
8
|
+
export const LATEST_SCENEVIEW_RELEASE = "4.18.0";
|
package/dist/guides.js
CHANGED
|
@@ -33,7 +33,7 @@ KMP shares **logic**, not **rendering**. Each platform uses its native renderer.
|
|
|
33
33
|
- **3D rendering** via Google Filament: PBR materials, HDR environments, glTF/GLB models, post-processing.
|
|
34
34
|
- **AR** via ARCore: plane detection, hit testing, anchors, cloud anchors, augmented images, depth, light estimation, point cloud.
|
|
35
35
|
- **Compose-native DSL**: all nodes are \`@Composable\` functions inside \`SceneView { }\` or \`ARSceneView { }\`.
|
|
36
|
-
- **
|
|
36
|
+
- **42+ node types**: ModelNode, LightNode, AnchorNode, CameraNode, TextNode, PathNode, ViewNode, PlaneNode, SphereNode, CylinderNode, CubeNode, DynamicSkyNode, FogNode, ReflectionProbeNode, PhysicsNode, BillboardNode, LineNode, and more.
|
|
37
37
|
|
|
38
38
|
## Apple — Alpha (SceneViewSwift)
|
|
39
39
|
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
17
17
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18
18
|
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
19
19
|
import { fetchKnownIssues } from "./issues.js";
|
|
20
|
+
import { DEMO_WITH_SETTINGS_EXAMPLE, SKETCHFAB_STREAMING_EXAMPLE, } from "./examples.js";
|
|
20
21
|
import { recordClientInit, recordToolCall } from "./telemetry.js";
|
|
21
22
|
import { isProTool, getToolTier } from "./tiers.js";
|
|
22
23
|
import { dispatchProxyToolCall, isProxyConfigured, DEFAULT_PRICING_URL, } from "./proxy.js";
|
|
@@ -66,6 +67,18 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
|
66
67
|
description: "Live list of open issues from the SceneView GitHub repository. Check this before reporting a bug or when something isn't working — there may already be a known workaround.",
|
|
67
68
|
mimeType: "text/markdown",
|
|
68
69
|
},
|
|
70
|
+
{
|
|
71
|
+
uri: "examples://demo-with-settings",
|
|
72
|
+
name: "Example — DemoScaffold v2 (full-screen scene + ModalBottomSheet)",
|
|
73
|
+
description: "Pattern for full-screen 3D / AR scene + Material 3 ModalBottomSheet controls. The DemoScaffold v2 contract used by every demo in samples/android-demo (issue #1154, PR #1169). Read this before adding a new demo with settings.",
|
|
74
|
+
mimeType: "text/markdown",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
uri: "examples://sketchfab-streaming",
|
|
78
|
+
name: "Example — Stream Sketchfab CC-BY models into a SceneView demo",
|
|
79
|
+
description: "Pattern for streaming CC-BY licensed glTF models from Sketchfab on demand instead of bundling 30 MB of GLBs in the APK. Uses SketchfabAssetResolver + SampleAssets registry + per-slug bundled fallback (Stage 2 of umbrella issue #1152). Read this before adding a streamed demo.",
|
|
80
|
+
mimeType: "text/markdown",
|
|
81
|
+
},
|
|
69
82
|
],
|
|
70
83
|
}));
|
|
71
84
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
@@ -80,6 +93,22 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
80
93
|
contents: [{ uri: "sceneview://known-issues", mimeType: "text/markdown", text: issues }],
|
|
81
94
|
};
|
|
82
95
|
}
|
|
96
|
+
case "examples://demo-with-settings":
|
|
97
|
+
return {
|
|
98
|
+
contents: [{
|
|
99
|
+
uri: "examples://demo-with-settings",
|
|
100
|
+
mimeType: "text/markdown",
|
|
101
|
+
text: DEMO_WITH_SETTINGS_EXAMPLE,
|
|
102
|
+
}],
|
|
103
|
+
};
|
|
104
|
+
case "examples://sketchfab-streaming":
|
|
105
|
+
return {
|
|
106
|
+
contents: [{
|
|
107
|
+
uri: "examples://sketchfab-streaming",
|
|
108
|
+
mimeType: "text/markdown",
|
|
109
|
+
text: SKETCHFAB_STREAMING_EXAMPLE,
|
|
110
|
+
}],
|
|
111
|
+
};
|
|
83
112
|
default:
|
|
84
113
|
throw new Error(`Unknown resource: ${request.params.uri}`);
|
|
85
114
|
}
|
package/dist/issues.js
CHANGED
|
@@ -151,10 +151,12 @@ export async function fetchKnownIssues() {
|
|
|
151
151
|
fetchError = "GitHub API returned unexpected response format (expected array).";
|
|
152
152
|
}
|
|
153
153
|
else {
|
|
154
|
-
issues = json
|
|
154
|
+
issues = json
|
|
155
|
+
.filter((item) => typeof item === "object" &&
|
|
155
156
|
item !== null &&
|
|
156
157
|
typeof item.number === "number" &&
|
|
157
|
-
typeof item.title === "string")
|
|
158
|
+
typeof item.title === "string")
|
|
159
|
+
.map(normalizeIssue);
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
}
|
|
@@ -165,6 +167,51 @@ export async function fetchKnownIssues() {
|
|
|
165
167
|
cache = { data: result, fetchedAt: now };
|
|
166
168
|
return result;
|
|
167
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Coerce a loosely-validated GitHub API item into a complete {@link GitHubIssue}.
|
|
172
|
+
*
|
|
173
|
+
* The upstream type guard only asserts `number` + `title`. A malformed API
|
|
174
|
+
* response — or a partial item served during a GitHub incident — can be
|
|
175
|
+
* missing `user`, `labels` or `updated_at`. Reading those unconditionally in
|
|
176
|
+
* `formatIssues` throws a `TypeError` and takes down the whole
|
|
177
|
+
* `sceneview://known-issues` resource. Default every optional field here so
|
|
178
|
+
* formatting can never crash.
|
|
179
|
+
*/
|
|
180
|
+
function normalizeIssue(item) {
|
|
181
|
+
const rawLabels = item.labels;
|
|
182
|
+
const labels = Array.isArray(rawLabels)
|
|
183
|
+
? rawLabels
|
|
184
|
+
.map((l) => {
|
|
185
|
+
if (typeof l === "string")
|
|
186
|
+
return { name: l };
|
|
187
|
+
if (l !== null && typeof l === "object") {
|
|
188
|
+
const name = l.name;
|
|
189
|
+
if (typeof name === "string")
|
|
190
|
+
return { name };
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
})
|
|
194
|
+
.filter((l) => l !== null)
|
|
195
|
+
: [];
|
|
196
|
+
const rawUser = item.user;
|
|
197
|
+
const login = rawUser !== null &&
|
|
198
|
+
typeof rawUser === "object" &&
|
|
199
|
+
typeof rawUser.login === "string"
|
|
200
|
+
? rawUser.login
|
|
201
|
+
: "unknown";
|
|
202
|
+
const updatedAt = typeof item.updated_at === "string" ? item.updated_at : "";
|
|
203
|
+
const createdAt = typeof item.created_at === "string" ? item.created_at : "";
|
|
204
|
+
return {
|
|
205
|
+
number: item.number,
|
|
206
|
+
title: item.title,
|
|
207
|
+
html_url: typeof item.html_url === "string" ? item.html_url : "",
|
|
208
|
+
body: typeof item.body === "string" ? item.body : null,
|
|
209
|
+
labels,
|
|
210
|
+
created_at: createdAt,
|
|
211
|
+
updated_at: updatedAt,
|
|
212
|
+
user: { login },
|
|
213
|
+
};
|
|
214
|
+
}
|
|
168
215
|
function formatIssues(issues, fetchError) {
|
|
169
216
|
const lines = ["# SceneView — Open GitHub Issues\n"];
|
|
170
217
|
if (fetchError) {
|
package/dist/telemetry.js
CHANGED
|
@@ -8,16 +8,25 @@
|
|
|
8
8
|
// - client: MCP client name (e.g. "claude-desktop", "cursor")
|
|
9
9
|
// - clientVersion: MCP client version string reported during handshake
|
|
10
10
|
// - mcpVersion: this package's version
|
|
11
|
-
// - tier: "free" | "pro"
|
|
11
|
+
// - tier: "free" | "pro" — tier the TOOL resolved to, NOT the user's plan
|
|
12
12
|
// - tool?: tool name (only for "tool" events)
|
|
13
|
+
// - installId?: opaque random UUID per install — lets the worker count
|
|
14
|
+
// unique developers per client runtime without identifying who they are
|
|
15
|
+
// - botLikelihood?: float in [0.0, 1.0] computed at the sender side from
|
|
16
|
+
// environment signals (CI flags, no-TTY, container hints). Worker uses
|
|
17
|
+
// it to exclude bot traffic from monetization analytics by default.
|
|
13
18
|
//
|
|
14
19
|
// What NEVER gets sent:
|
|
15
20
|
// - IP address (the endpoint strips it server-side; we never send headers)
|
|
16
|
-
// - hostname, OS user, machine id
|
|
21
|
+
// - hostname, OS user, machine id, cwd, project path, package name
|
|
17
22
|
// - prompt content, tool arguments, tool results
|
|
18
23
|
// - API keys, billing info
|
|
19
24
|
//
|
|
20
25
|
// Opt out with `SCENEVIEW_TELEMETRY=0` or by running in CI (`CI=true`).
|
|
26
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { homedir } from "node:os";
|
|
28
|
+
import { join } from "node:path";
|
|
29
|
+
import { randomUUID } from "node:crypto";
|
|
21
30
|
// Worker implementation: telemetry-worker/ (Hono + D1 + KV rate limiting).
|
|
22
31
|
// Deploy with: cd telemetry-worker && see DEPLOY.md
|
|
23
32
|
const TELEMETRY_ENDPOINT = "https://sceneview-telemetry.mcp-tools-lab.workers.dev/v1/events";
|
|
@@ -31,6 +40,9 @@ let clientContext;
|
|
|
31
40
|
// ─── Client-side batch buffer ─────────────────────────────────────────────────
|
|
32
41
|
let buffer = [];
|
|
33
42
|
let flushTimer;
|
|
43
|
+
// Cached fingerprint values — computed once per process.
|
|
44
|
+
let cachedInstallId;
|
|
45
|
+
let cachedBotLikelihood;
|
|
34
46
|
// Read lazily so tests that mutate env vars between runs see the latest value.
|
|
35
47
|
function isEnabled() {
|
|
36
48
|
if (process.env.SCENEVIEW_TELEMETRY === "0")
|
|
@@ -47,6 +59,156 @@ import { PACKAGE_VERSION } from "./generated/version.js";
|
|
|
47
59
|
function getMcpVersion() {
|
|
48
60
|
return process.env.SCENEVIEW_MCP_VERSION ?? PACKAGE_VERSION;
|
|
49
61
|
}
|
|
62
|
+
// ─── Install ID — anonymous per-install fingerprint ──────────────────────────
|
|
63
|
+
//
|
|
64
|
+
// Stored at `$XDG_CONFIG_HOME/sceneview-mcp/install.json` (default
|
|
65
|
+
// `~/.config/sceneview-mcp/install.json`). Created on first run if missing.
|
|
66
|
+
// Contents: `{ "installId": "<uuid v4>", "createdAt": "<ISO>" }`.
|
|
67
|
+
//
|
|
68
|
+
// Why a UUID and not a hash of hostname / cwd / etc.:
|
|
69
|
+
// - hostnames/cwd are personally identifying (employee laptop name,
|
|
70
|
+
// `/Users/<name>/...`) — even hashed, they're predictable enough that
|
|
71
|
+
// a determined attacker with worker-side DB access could brute-force
|
|
72
|
+
// them back. A UUID is provably non-identifying.
|
|
73
|
+
// - The point is to count unique developers per `client` runtime, not to
|
|
74
|
+
// fingerprint the host. A fresh install = a fresh identity, by design.
|
|
75
|
+
//
|
|
76
|
+
// Disabled when telemetry is disabled (no file is written).
|
|
77
|
+
function getInstallIdConfigPath() {
|
|
78
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
79
|
+
const base = xdg && xdg.length > 0 ? xdg : join(homedir(), ".config");
|
|
80
|
+
return join(base, "sceneview-mcp", "install.json");
|
|
81
|
+
}
|
|
82
|
+
function loadOrCreateInstallId() {
|
|
83
|
+
if (cachedInstallId !== undefined)
|
|
84
|
+
return cachedInstallId;
|
|
85
|
+
let path;
|
|
86
|
+
try {
|
|
87
|
+
path = getInstallIdConfigPath();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
// Try to read an existing install record.
|
|
93
|
+
try {
|
|
94
|
+
const raw = readFileSync(path, "utf8");
|
|
95
|
+
const parsed = JSON.parse(raw);
|
|
96
|
+
if (typeof parsed.installId === "string" && parsed.installId.length > 0) {
|
|
97
|
+
cachedInstallId = parsed.installId;
|
|
98
|
+
return cachedInstallId;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// File missing or unreadable — fall through to create.
|
|
103
|
+
}
|
|
104
|
+
// Create a fresh install record. All failures are swallowed: a read-only
|
|
105
|
+
// home directory (CI sandboxes, container images) should not break telemetry
|
|
106
|
+
// or the MCP server itself.
|
|
107
|
+
try {
|
|
108
|
+
const installId = randomUUID();
|
|
109
|
+
const dir = join(path, "..");
|
|
110
|
+
mkdirSync(dir, { recursive: true });
|
|
111
|
+
writeFileSync(path, JSON.stringify({ installId, createdAt: new Date().toISOString() }, null, 2), { encoding: "utf8" });
|
|
112
|
+
cachedInstallId = installId;
|
|
113
|
+
return cachedInstallId;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Fallback: an ephemeral per-process UUID. Better than nothing —
|
|
117
|
+
// still distinguishes parallel scripted runs from a real install.
|
|
118
|
+
cachedInstallId = randomUUID();
|
|
119
|
+
return cachedInstallId;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// ─── Bot likelihood — sender-side heuristic ──────────────────────────────────
|
|
123
|
+
//
|
|
124
|
+
// Returns a score in [0.0, 1.0] where 1.0 = almost certainly automation.
|
|
125
|
+
// Signals (each independently bumps the score):
|
|
126
|
+
//
|
|
127
|
+
// - GITHUB_ACTIONS / GITLAB_CI / CIRCLECI / BUILDKITE / TF_BUILD / JENKINS_URL
|
|
128
|
+
// are set → +0.45 (strong)
|
|
129
|
+
// - CI is set to any truthy value → +0.30 (medium)
|
|
130
|
+
// - stdout is not a TTY → +0.15 (weak)
|
|
131
|
+
// - common container/sandbox hints
|
|
132
|
+
// (KUBERNETES_SERVICE_HOST, container=docker, AWS_LAMBDA_FUNCTION_NAME)
|
|
133
|
+
// → +0.20 (medium)
|
|
134
|
+
// - DEBIAN_FRONTEND=noninteractive (popular in scripts) → +0.10 (weak)
|
|
135
|
+
//
|
|
136
|
+
// The score is computed ONCE per process and reused on every event. It is
|
|
137
|
+
// clamped to [0.0, 1.0]. The worker uses it as an advisory exclusion filter
|
|
138
|
+
// (default threshold 0.7); raw events are still stored for forensics.
|
|
139
|
+
function computeBotLikelihood() {
|
|
140
|
+
if (cachedBotLikelihood !== undefined)
|
|
141
|
+
return cachedBotLikelihood;
|
|
142
|
+
let score = 0;
|
|
143
|
+
const env = process.env;
|
|
144
|
+
// Strong: a named CI provider env var.
|
|
145
|
+
const strongCiVars = [
|
|
146
|
+
"GITHUB_ACTIONS",
|
|
147
|
+
"GITLAB_CI",
|
|
148
|
+
"CIRCLECI",
|
|
149
|
+
"BUILDKITE",
|
|
150
|
+
"TF_BUILD",
|
|
151
|
+
"JENKINS_URL",
|
|
152
|
+
"BITBUCKET_BUILD_NUMBER",
|
|
153
|
+
"TEAMCITY_VERSION",
|
|
154
|
+
"TRAVIS",
|
|
155
|
+
"DRONE",
|
|
156
|
+
];
|
|
157
|
+
if (strongCiVars.some((k) => typeof env[k] === "string" && env[k].length > 0)) {
|
|
158
|
+
score += 0.45;
|
|
159
|
+
}
|
|
160
|
+
// Medium: generic CI=truthy. We don't double-count if a strong var fired,
|
|
161
|
+
// but for cheap envs ("CI=true" alone), this captures them.
|
|
162
|
+
const ci = env.CI;
|
|
163
|
+
if (typeof ci === "string" && (ci === "true" || ci === "1")) {
|
|
164
|
+
score += 0.3;
|
|
165
|
+
}
|
|
166
|
+
// Weak: no TTY → almost always automation or piped invocation. We test
|
|
167
|
+
// stdout because the MCP protocol uses stdin/stdout, but stderr is the
|
|
168
|
+
// more reliable indicator of "is a human watching".
|
|
169
|
+
try {
|
|
170
|
+
// process.stderr is most reliable; some MCP runners pipe stdout but leave
|
|
171
|
+
// stderr attached. Both being non-TTY is a stronger bot signal.
|
|
172
|
+
const stderrTty = typeof process.stderr === "object" &&
|
|
173
|
+
typeof process.stderr.isTTY === "boolean" &&
|
|
174
|
+
process.stderr.isTTY === true;
|
|
175
|
+
if (!stderrTty)
|
|
176
|
+
score += 0.15;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Defensive — if stderr isn't introspectable, treat as non-TTY.
|
|
180
|
+
score += 0.15;
|
|
181
|
+
}
|
|
182
|
+
// Medium: containerized environments.
|
|
183
|
+
const containerVars = [
|
|
184
|
+
"KUBERNETES_SERVICE_HOST",
|
|
185
|
+
"AWS_LAMBDA_FUNCTION_NAME",
|
|
186
|
+
"VERCEL",
|
|
187
|
+
"NETLIFY",
|
|
188
|
+
"CODESPACE_NAME",
|
|
189
|
+
];
|
|
190
|
+
if (containerVars.some((k) => typeof env[k] === "string" && env[k].length > 0) ||
|
|
191
|
+
env.container === "docker") {
|
|
192
|
+
score += 0.2;
|
|
193
|
+
}
|
|
194
|
+
// Weak: scripted invocations frequently set this.
|
|
195
|
+
if (env.DEBIAN_FRONTEND === "noninteractive")
|
|
196
|
+
score += 0.1;
|
|
197
|
+
cachedBotLikelihood = Math.min(1, Math.max(0, score));
|
|
198
|
+
return cachedBotLikelihood;
|
|
199
|
+
}
|
|
200
|
+
// Build a base payload populated with the install fingerprint + bot score.
|
|
201
|
+
// `installId` is omitted when telemetry is disabled or the install file
|
|
202
|
+
// could not be created. `botLikelihood` is always present (default 0).
|
|
203
|
+
function buildBase() {
|
|
204
|
+
const out = {
|
|
205
|
+
botLikelihood: computeBotLikelihood(),
|
|
206
|
+
};
|
|
207
|
+
const installId = loadOrCreateInstallId();
|
|
208
|
+
if (installId)
|
|
209
|
+
out.installId = installId;
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
50
212
|
// Fire-and-forget POST of a single payload to the individual event endpoint.
|
|
51
213
|
// Used as fallback when batch delivery fails.
|
|
52
214
|
function sendSingle(payload) {
|
|
@@ -148,6 +310,8 @@ function send(payload) {
|
|
|
148
310
|
/** Exposed for tests — resets the cached client context and the batch buffer. */
|
|
149
311
|
export function __resetClientContext() {
|
|
150
312
|
clientContext = undefined;
|
|
313
|
+
cachedInstallId = undefined;
|
|
314
|
+
cachedBotLikelihood = undefined;
|
|
151
315
|
buffer = [];
|
|
152
316
|
if (flushTimer !== undefined) {
|
|
153
317
|
clearTimeout(flushTimer);
|
|
@@ -175,6 +339,7 @@ export function recordClientInit(clientInfo) {
|
|
|
175
339
|
clientVersion: clientContext.clientVersion,
|
|
176
340
|
mcpVersion: getMcpVersion(),
|
|
177
341
|
tier: "free",
|
|
342
|
+
...buildBase(),
|
|
178
343
|
});
|
|
179
344
|
}
|
|
180
345
|
/**
|
|
@@ -196,5 +361,16 @@ export function recordToolCall(toolName, tier) {
|
|
|
196
361
|
mcpVersion: getMcpVersion(),
|
|
197
362
|
tier,
|
|
198
363
|
tool: toolName,
|
|
364
|
+
...buildBase(),
|
|
199
365
|
});
|
|
200
366
|
}
|
|
367
|
+
// ─── Test-only exports ────────────────────────────────────────────────────────
|
|
368
|
+
/** Exposed for tests — bypass the cache and recompute the bot score. */
|
|
369
|
+
export function __computeBotLikelihoodForTest() {
|
|
370
|
+
cachedBotLikelihood = undefined;
|
|
371
|
+
return computeBotLikelihood();
|
|
372
|
+
}
|
|
373
|
+
/** Exposed for tests — read the install ID without going through send(). */
|
|
374
|
+
export function __getInstallIdForTest() {
|
|
375
|
+
return loadOrCreateInstallId();
|
|
376
|
+
}
|
package/dist/tiers.js
CHANGED
|
@@ -604,4 +604,42 @@ export const TOOL_DEFINITIONS = [
|
|
|
604
604
|
destructiveHint: false,
|
|
605
605
|
},
|
|
606
606
|
},
|
|
607
|
+
{
|
|
608
|
+
name: "search_android_docs",
|
|
609
|
+
description: "Searches Google's stock Android documentation knowledge base (~4 800 entries: Jetpack Compose, Camera2, ARCore SDK, Kotlin APIs, platform guides) and returns matching entries with their `kb://...` URIs. Use this to cross-reference stock Android APIs with SceneView code — e.g. \"how does LazyColumn paging work\", \"Camera2 capture session\", \"ARCore Config options\". Then call `fetch_android_doc` with a returned URI to read the full entry. Requires Google's `android` CLI on the MCP host's PATH (an optional runtime dependency — sceneview-mcp works without it); if the CLI is absent the tool returns clear install instructions instead of crashing.",
|
|
610
|
+
inputSchema: {
|
|
611
|
+
type: "object",
|
|
612
|
+
properties: {
|
|
613
|
+
query: {
|
|
614
|
+
type: "string",
|
|
615
|
+
description: "Free-text search query, e.g. \"LazyColumn paging\", \"Camera2 capture session\", \"ARCore anchors\".",
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
required: ["query"],
|
|
619
|
+
},
|
|
620
|
+
annotations: {
|
|
621
|
+
readOnlyHint: true,
|
|
622
|
+
openWorldHint: true,
|
|
623
|
+
destructiveHint: false,
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
name: "fetch_android_doc",
|
|
628
|
+
description: "Fetches the full text of a single stock Android documentation entry by its knowledge-base URI (`kb://...`), as returned by `search_android_docs`. Use this after `search_android_docs` to read a complete guide or API reference. Requires Google's `android` CLI on the MCP host's PATH (an optional runtime dependency); if the CLI is absent the tool returns clear install instructions instead of crashing.",
|
|
629
|
+
inputSchema: {
|
|
630
|
+
type: "object",
|
|
631
|
+
properties: {
|
|
632
|
+
uri: {
|
|
633
|
+
type: "string",
|
|
634
|
+
description: "The knowledge-base URI to fetch, e.g. \"kb://compose/lists/lazy-column\". A bare path with no scheme is tolerated and normalised to kb://.",
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
required: ["uri"],
|
|
638
|
+
},
|
|
639
|
+
annotations: {
|
|
640
|
+
readOnlyHint: true,
|
|
641
|
+
openWorldHint: true,
|
|
642
|
+
destructiveHint: false,
|
|
643
|
+
},
|
|
644
|
+
},
|
|
607
645
|
];
|
package/dist/tools/handler.js
CHANGED
|
@@ -34,6 +34,7 @@ import { ANIMATION_GUIDE, GESTURE_GUIDE, PERFORMANCE_TIPS, } from "../advanced-g
|
|
|
34
34
|
import { MATERIAL_GUIDE, COLLISION_GUIDE, MODEL_OPTIMIZATION_GUIDE, WEB_RENDERING_GUIDE, } from "../extra-guides.js";
|
|
35
35
|
import { searchModels, formatSearchResults } from "../search-models.js";
|
|
36
36
|
import { analyzeProject, formatAnalysisReport } from "../analyze-project.js";
|
|
37
|
+
import { searchAndroidDocs, fetchAndroidDoc, formatAndroidDocsSearch, formatAndroidDocsFetch, } from "../android-docs.js";
|
|
37
38
|
import { LLMS_TXT } from "../generated/llms-txt.js";
|
|
38
39
|
import { LATEST_SCENEVIEW_RELEASE } from "../generated/version.js";
|
|
39
40
|
// ─── Legal disclaimer (identical to index.ts 4.0.0) ─────────────────────
|
|
@@ -789,6 +790,38 @@ export async function dispatchTool(toolName, args, _ctx = {}) {
|
|
|
789
790
|
};
|
|
790
791
|
}
|
|
791
792
|
}
|
|
793
|
+
// ── search_android_docs ──────────────────────────────────────────────────
|
|
794
|
+
case "search_android_docs": {
|
|
795
|
+
const query = args?.query;
|
|
796
|
+
if (!query || typeof query !== "string" || query.trim().length === 0) {
|
|
797
|
+
return {
|
|
798
|
+
content: [{ type: "text", text: "Missing required parameter: `query` must be a non-empty string." }],
|
|
799
|
+
isError: true,
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
const docsResult = await searchAndroidDocs(query);
|
|
803
|
+
const text = formatAndroidDocsSearch(query, docsResult);
|
|
804
|
+
return {
|
|
805
|
+
content: withDisclaimer([{ type: "text", text }]),
|
|
806
|
+
isError: docsResult.ok ? undefined : true,
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
// ── fetch_android_doc ────────────────────────────────────────────────────
|
|
810
|
+
case "fetch_android_doc": {
|
|
811
|
+
const uri = args?.uri;
|
|
812
|
+
if (!uri || typeof uri !== "string" || uri.trim().length === 0) {
|
|
813
|
+
return {
|
|
814
|
+
content: [{ type: "text", text: "Missing required parameter: `uri` must be a non-empty `kb://...` string." }],
|
|
815
|
+
isError: true,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
const docResult = await fetchAndroidDoc(uri);
|
|
819
|
+
const text = formatAndroidDocsFetch(uri, docResult);
|
|
820
|
+
return {
|
|
821
|
+
content: withDisclaimer([{ type: "text", text }]),
|
|
822
|
+
isError: docResult.ok ? undefined : true,
|
|
823
|
+
};
|
|
824
|
+
}
|
|
792
825
|
default:
|
|
793
826
|
return {
|
|
794
827
|
content: [{ type: "text", text: `Unknown tool: ${toolName}` }],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sceneview-mcp",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.13",
|
|
4
4
|
"mcpName": "io.github.sceneview/mcp",
|
|
5
5
|
"description": "MCP server for SceneView — cross-platform 3D & AR SDK for Android and iOS. Give Claude the full SceneView SDK so it writes correct, compilable code.",
|
|
6
6
|
"keywords": [
|
|
@@ -64,12 +64,15 @@
|
|
|
64
64
|
"prepare": "node scripts/generate-llms-txt.js && node scripts/generate-version.js && tsc",
|
|
65
65
|
"start": "node dist/index.js",
|
|
66
66
|
"dev": "tsx src/index.ts",
|
|
67
|
-
"test": "node scripts/generate-llms-txt.js && node scripts/generate-version.js && vitest run"
|
|
67
|
+
"test": "node scripts/generate-llms-txt.js && node scripts/generate-version.js && vitest run",
|
|
68
|
+
"biome": "cd .. && mcp/node_modules/.bin/biome check",
|
|
69
|
+
"biome:fix": "cd .. && mcp/node_modules/.bin/biome check --write"
|
|
68
70
|
},
|
|
69
71
|
"dependencies": {
|
|
70
72
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
71
73
|
},
|
|
72
74
|
"devDependencies": {
|
|
75
|
+
"@biomejs/biome": "^2.0.0",
|
|
73
76
|
"@types/node": "^25.5.0",
|
|
74
77
|
"tsx": "^4.0.0",
|
|
75
78
|
"typescript": "^6.0.2",
|