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.
@@ -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.12";
8
- export const LATEST_SCENEVIEW_RELEASE = "4.1.0";
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
- - **26+ node types**: ModelNode, LightNode, AnchorNode, CameraNode, TextNode, PathNode, ViewNode, PlaneNode, SphereNode, CylinderNode, CubeNode, DynamicSkyNode, FogNode, ReflectionProbeNode, PhysicsNode, BillboardNode, LineNode, and more.
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.filter((item) => typeof item === "object" &&
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
@@ -29,6 +29,8 @@ const FREE_TOOLS = [
29
29
  "get_platform_roadmap",
30
30
  "search_models",
31
31
  "analyze_project",
32
+ "search_android_docs",
33
+ "fetch_android_doc",
32
34
  // Setup guides (moved from Pro in 4.0.5)
33
35
  "get_ios_setup",
34
36
  "get_web_setup",
@@ -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
  ];
@@ -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.12",
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",