unity-open-mcp 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -73
- package/dist/batch-spawn.d.ts +4 -0
- package/dist/batch-spawn.js +73 -18
- package/dist/batch-spawn.js.map +1 -1
- package/dist/capabilities/build-capabilities.d.ts +5 -5
- package/dist/capabilities/build-capabilities.js +2 -2
- package/dist/capabilities/cost-hints.js +20 -0
- package/dist/capabilities/cost-hints.js.map +1 -1
- package/dist/capabilities/lifecycle.js +1 -1
- package/dist/capabilities/lifecycle.js.map +1 -1
- package/dist/capabilities/tool-groups.d.ts +6 -9
- package/dist/capabilities/tool-groups.js +11 -13
- package/dist/capabilities/tool-groups.js.map +1 -1
- package/dist/cli/commands.js +9 -8
- package/dist/cli/commands.js.map +1 -1
- package/dist/compressible-router.js +6 -0
- package/dist/compressible-router.js.map +1 -1
- package/dist/constants.d.ts +89 -0
- package/dist/constants.js +109 -0
- package/dist/constants.js.map +1 -0
- package/dist/dialog-dismiss.d.ts +0 -7
- package/dist/dialog-dismiss.js +17 -19
- package/dist/dialog-dismiss.js.map +1 -1
- package/dist/hub-control.d.ts +6 -2
- package/dist/hub-control.js +8 -8
- package/dist/hub-control.js.map +1 -1
- package/dist/index.js +12 -13
- package/dist/index.js.map +1 -1
- package/dist/instance-discovery.js +2 -1
- package/dist/instance-discovery.js.map +1 -1
- package/dist/live-client.d.ts +49 -0
- package/dist/live-client.js +292 -255
- package/dist/live-client.js.map +1 -1
- package/dist/offline/api.d.ts +171 -0
- package/dist/offline/api.js +855 -0
- package/dist/offline/api.js.map +1 -0
- package/dist/offline/hierarchy.d.ts +24 -0
- package/dist/offline/hierarchy.js +242 -0
- package/dist/offline/hierarchy.js.map +1 -0
- package/dist/offline/index-builders.d.ts +11 -0
- package/dist/offline/index-builders.js +202 -0
- package/dist/offline/index-builders.js.map +1 -0
- package/dist/offline/model.d.ts +8 -0
- package/dist/offline/model.js +188 -0
- package/dist/offline/model.js.map +1 -0
- package/dist/offline/names.d.ts +7 -0
- package/dist/offline/names.js +20 -0
- package/dist/offline/names.js.map +1 -0
- package/dist/offline/overrides.d.ts +17 -0
- package/dist/offline/overrides.js +266 -0
- package/dist/offline/overrides.js.map +1 -0
- package/dist/offline/parse.d.ts +25 -0
- package/dist/offline/parse.js +236 -0
- package/dist/offline/parse.js.map +1 -0
- package/dist/offline/paths.d.ts +4 -0
- package/dist/offline/paths.js +23 -0
- package/dist/offline/paths.js.map +1 -0
- package/dist/offline/primitives.d.ts +17 -0
- package/dist/offline/primitives.js +195 -0
- package/dist/offline/primitives.js.map +1 -0
- package/dist/offline/references.d.ts +9 -0
- package/dist/offline/references.js +55 -0
- package/dist/offline/references.js.map +1 -0
- package/dist/offline/types.d.ts +65 -0
- package/dist/offline/types.js +154 -0
- package/dist/offline/types.js.map +1 -0
- package/dist/offline.d.ts +2 -180
- package/dist/offline.js +27 -2282
- package/dist/offline.js.map +1 -1
- package/dist/project-health.d.ts +1 -1
- package/dist/project-health.js +66 -3
- package/dist/project-health.js.map +1 -1
- package/dist/routers.js +6 -5
- package/dist/routers.js.map +1 -1
- package/dist/tool-router.d.ts +13 -0
- package/dist/tool-router.js +179 -251
- package/dist/tool-router.js.map +1 -1
- package/dist/tool-session-state.js +18 -9
- package/dist/tool-session-state.js.map +1 -1
- package/dist/tools/apply-fix.js +7 -2
- package/dist/tools/apply-fix.js.map +1 -1
- package/dist/tools/batch-execute.js +8 -2
- package/dist/tools/batch-execute.js.map +1 -1
- package/dist/tools/bridge-status.js +1 -2
- package/dist/tools/bridge-status.js.map +1 -1
- package/dist/tools/checkpoint-create.js +1 -0
- package/dist/tools/checkpoint-create.js.map +1 -1
- package/dist/tools/component-get.js +44 -11
- package/dist/tools/component-get.js.map +1 -1
- package/dist/tools/delta.js +1 -0
- package/dist/tools/delta.js.map +1 -1
- package/dist/tools/dependencies.js +4 -1
- package/dist/tools/dependencies.js.map +1 -1
- package/dist/tools/editor-status.js +3 -2
- package/dist/tools/editor-status.js.map +1 -1
- package/dist/tools/execute-csharp.js +3 -2
- package/dist/tools/execute-csharp.js.map +1 -1
- package/dist/tools/find-references.js +8 -1
- package/dist/tools/find-references.js.map +1 -1
- package/dist/tools/index.js +19 -17
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inputsystem-asset-create.js +4 -4
- package/dist/tools/inputsystem-asset-create.js.map +1 -1
- package/dist/tools/invoke-method.js +2 -1
- package/dist/tools/invoke-method.js.map +1 -1
- package/dist/tools/manage-tools.js +10 -10
- package/dist/tools/manage-tools.js.map +1 -1
- package/dist/tools/navigation-surface-add.js +6 -5
- package/dist/tools/navigation-surface-add.js.map +1 -1
- package/dist/tools/probuilder-create-shape.js +4 -4
- package/dist/tools/probuilder-create-shape.js.map +1 -1
- package/dist/tools/read-compile-errors.js +44 -31
- package/dist/tools/read-compile-errors.js.map +1 -1
- package/dist/tools/scene-get-data.js +10 -2
- package/dist/tools/scene-get-data.js.map +1 -1
- package/dist/tools/scene-save.js +16 -5
- package/dist/tools/scene-save.js.map +1 -1
- package/dist/tools/scene-set-active.js +22 -6
- package/dist/tools/scene-set-active.js.map +1 -1
- package/dist/tools/scene-unload.js +20 -6
- package/dist/tools/scene-unload.js.map +1 -1
- package/dist/tools/search-assets.js +3 -1
- package/dist/tools/search-assets.js.map +1 -1
- package/package.json +1 -1
package/dist/tool-router.js
CHANGED
|
@@ -10,6 +10,7 @@ import { generateSkill } from "./skill/generate-skill.js";
|
|
|
10
10
|
import { knownClientKeys } from "./skill/client-paths.js";
|
|
11
11
|
import { ALL_TOOLS } from "./tools/index.js";
|
|
12
12
|
import { lockPath, readInstanceLock, classifyInstance, isPidAlive } from "./instance-discovery.js";
|
|
13
|
+
import { PORT_ENV_VAR } from "./constants.js";
|
|
13
14
|
import { findUnityForProject } from "./running-unity.js";
|
|
14
15
|
import { listInstalledEditors, fetchAvailableReleases, openInstallDeepLink, getInstallPath, setInstallPath, } from "./hub-control.js";
|
|
15
16
|
/** Default Hub-control backend — calls the real hub-control functions. */
|
|
@@ -20,8 +21,50 @@ const defaultHubBackend = {
|
|
|
20
21
|
getInstallPath: () => getInstallPath(),
|
|
21
22
|
setInstallPath: (path) => setInstallPath(path),
|
|
22
23
|
};
|
|
23
|
-
import { BATCH_TOOL_NAMES } from "./batch-spawn.js";
|
|
24
|
+
import { BATCH_TOOL_NAMES, ALWAYS_BATCH_TOOLS } from "./batch-spawn.js";
|
|
24
25
|
import { readProfileAndDetail, applyPaging, attachPagination, foldVerifyResult, parseResultBody, withResultBody, } from "./output-profile.js";
|
|
26
|
+
/**
|
|
27
|
+
* Single source of truth for the `_source` tag. Every route-tagging site goes
|
|
28
|
+
* through one of the two helpers below so live / offline / local stay
|
|
29
|
+
* symmetric — a new route adding `_source` inline (the omission that slipped
|
|
30
|
+
* through before T6.1) is caught by the "no inline `_source` outside the
|
|
31
|
+
* helper" rule and grep.
|
|
32
|
+
*
|
|
33
|
+
* - {@link withSource} stamps `_source` on a body object literal (the local /
|
|
34
|
+
* offline paths that build the JSON themselves).
|
|
35
|
+
* - {@link tagSource} stamps `_source` on an already-built {@link
|
|
36
|
+
* CallToolResult} (the live / offline-result paths that wrap a bridge or
|
|
37
|
+
* disk result).
|
|
38
|
+
*/
|
|
39
|
+
function withSource(body, source) {
|
|
40
|
+
return { ...body, _source: source };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Tag a CallToolResult's JSON body with `_source` so callers switching on the
|
|
44
|
+
* field see a defined value for live-routed drill-downs (find_references /
|
|
45
|
+
* dependencies). A no-op when the first content block is not parseable JSON;
|
|
46
|
+
* preserves `isError` and other content blocks.
|
|
47
|
+
*/
|
|
48
|
+
function tagSource(result, source) {
|
|
49
|
+
const body = parseResultBody(result);
|
|
50
|
+
if (body === null)
|
|
51
|
+
return result;
|
|
52
|
+
return withResultBody(result, withSource(body, source));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build a single-block CallToolResult from a body object, tagging it with
|
|
56
|
+
* `_source`. The common shape for local/offline routes that synthesize the
|
|
57
|
+
* entire response in the MCP server (no bridge tool endpoint). Accepts the
|
|
58
|
+
* typed result shapes (ListAssetsResult, CapabilitiesResult, …) returned by
|
|
59
|
+
* the offline/local builders.
|
|
60
|
+
*/
|
|
61
|
+
function sourceResult(body, source, isError = false) {
|
|
62
|
+
const tagged = withSource(body, source);
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: "text", text: JSON.stringify(tagged) }],
|
|
65
|
+
isError,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
25
68
|
function injectRouteMeta(result, meta) {
|
|
26
69
|
if (result.content.length === 0)
|
|
27
70
|
return result;
|
|
@@ -50,12 +93,7 @@ function injectRouteMeta(result, meta) {
|
|
|
50
93
|
// missing-parameter refusals before any side effect runs). Tagged _source=local
|
|
51
94
|
// because it is resolved entirely in the MCP server.
|
|
52
95
|
function localError(code, message) {
|
|
53
|
-
return {
|
|
54
|
-
content: [
|
|
55
|
-
{ type: "text", text: JSON.stringify({ error: { code, message }, _source: "local" }) },
|
|
56
|
-
],
|
|
57
|
-
isError: true,
|
|
58
|
-
};
|
|
96
|
+
return sourceResult({ error: { code, message } }, "local", true);
|
|
59
97
|
}
|
|
60
98
|
// M26 Plan 2 — gate-consistent envelope shape for the mutating Hub control
|
|
61
99
|
// tools (install_editor / install_modules / set_install_path). These are
|
|
@@ -63,7 +101,7 @@ function localError(code, message) {
|
|
|
63
101
|
// gate is skipped (gate.skipped=true). The shape matches MutationEnvelope so
|
|
64
102
|
// agents parse it uniformly alongside bridge-routed mutating tools.
|
|
65
103
|
function hubMutationEnvelope(opts) {
|
|
66
|
-
|
|
104
|
+
return sourceResult({
|
|
67
105
|
mutation: {
|
|
68
106
|
success: opts.success,
|
|
69
107
|
output: opts.output,
|
|
@@ -76,12 +114,7 @@ function hubMutationEnvelope(opts) {
|
|
|
76
114
|
delta: null,
|
|
77
115
|
},
|
|
78
116
|
agentNextSteps: opts.nextSteps,
|
|
79
|
-
|
|
80
|
-
};
|
|
81
|
-
return {
|
|
82
|
-
content: [{ type: "text", text: JSON.stringify(body) }],
|
|
83
|
-
isError: opts.isError,
|
|
84
|
-
};
|
|
117
|
+
}, "local", opts.isError);
|
|
85
118
|
}
|
|
86
119
|
function activeGroupsEqual(a, b) {
|
|
87
120
|
if (a.length !== b.length)
|
|
@@ -239,6 +272,50 @@ export class ToolRouter {
|
|
|
239
272
|
onToolListChanged;
|
|
240
273
|
hubBackend;
|
|
241
274
|
modelCache = new AssetModelCache();
|
|
275
|
+
/**
|
|
276
|
+
* M28-refactoring Plan 3 (T3.2) — named-tool dispatch map. Replaces the
|
|
277
|
+
* former ~70-block `if (toolName === "...")` chain in routeCore. Each entry
|
|
278
|
+
* binds the tool name to its route-handler method. The map is built lazily on
|
|
279
|
+
* first use (the handlers are bound instance methods, so it cannot be a
|
|
280
|
+
* static field). Adding a new named-tool route is now a map entry, not a new
|
|
281
|
+
* `if` block. Tools that share a handler (validate_edit + scan_paths) or are
|
|
282
|
+
* reached via a predicate (isCompressible) are handled below the lookup — the
|
|
283
|
+
* map covers only exact-name dispatch.
|
|
284
|
+
*/
|
|
285
|
+
routeHandlers;
|
|
286
|
+
getRouteHandlers() {
|
|
287
|
+
if (this.routeHandlers)
|
|
288
|
+
return this.routeHandlers;
|
|
289
|
+
const handlers = new Map([
|
|
290
|
+
// Offline-routed tools (resolved from disk; never hit the bridge).
|
|
291
|
+
["unity_open_mcp_list_assets", (_l, a) => this.routeListAssets(a)],
|
|
292
|
+
["unity_open_mcp_read_compile_errors", (_l, a) => this.routeReadCompileErrors(a)],
|
|
293
|
+
// Local-routed tools (server-only; no live/batch hop).
|
|
294
|
+
["unity_senses_pull_events", (l, a) => this.routePullEvents(a, l)],
|
|
295
|
+
["unity_open_mcp_capabilities", (l, a) => this.routeCapabilities(a, l)],
|
|
296
|
+
["unity_open_mcp_list_rules", (_l, a) => this.routeListRules(a)],
|
|
297
|
+
["unity_open_mcp_generate_skill", (_l, a) => this.routeGenerateSkill(a)],
|
|
298
|
+
["unity_open_mcp_manage_tools", (l, a) => this.routeManageTools(a, l)],
|
|
299
|
+
["unity_open_mcp_bridge_status", (l, a) => this.routeBridgeStatus(a, l)],
|
|
300
|
+
// Hub control tools (local-routed; system-level ops).
|
|
301
|
+
["unity_open_mcp_hub_list_editors", () => this.routeHubListEditors()],
|
|
302
|
+
["unity_open_mcp_hub_available_releases", () => this.routeHubAvailableReleases()],
|
|
303
|
+
["unity_open_mcp_hub_install_editor", (_l, a) => this.routeHubInstallEditor(a)],
|
|
304
|
+
["unity_open_mcp_hub_install_modules", (_l, a) => this.routeHubInstallModules(a)],
|
|
305
|
+
["unity_open_mcp_hub_get_install_path", () => this.routeHubGetInstallPath()],
|
|
306
|
+
["unity_open_mcp_hub_set_install_path", (_l, a) => this.routeHubSetInstallPath(a)],
|
|
307
|
+
// Offline-first reference tools (fall back to live when the bridge is up).
|
|
308
|
+
["unity_open_mcp_find_references", (l, a) => this.routeFindReferences(a, l)],
|
|
309
|
+
["unity_open_mcp_dependencies", (l, a) => this.routeDependencies(a, l)],
|
|
310
|
+
// Verify tools — share a single handler (toolName carried through).
|
|
311
|
+
["unity_open_mcp_validate_edit", (l, a) => this.routeVerifyResult("unity_open_mcp_validate_edit", a, l)],
|
|
312
|
+
["unity_open_mcp_scan_paths", (l, a) => this.routeVerifyResult("unity_open_mcp_scan_paths", a, l)],
|
|
313
|
+
// Scene data — output-profile + paging applied server-side.
|
|
314
|
+
["unity_open_mcp_scene_get_data", (l, a) => this.routeSceneGetData(a, l)],
|
|
315
|
+
]);
|
|
316
|
+
this.routeHandlers = handlers;
|
|
317
|
+
return handlers;
|
|
318
|
+
}
|
|
242
319
|
constructor(live, batch, projectPath, eventStream, sessionState, onToolListChanged,
|
|
243
320
|
// M26 Plan 2 — injectable Hub-control backend. Omit in production (the
|
|
244
321
|
// default calls the real hub-control functions); tests pass a fake to
|
|
@@ -267,101 +344,13 @@ export class ToolRouter {
|
|
|
267
344
|
return this.routeCore(overrideLive, toolName, args);
|
|
268
345
|
}
|
|
269
346
|
async routeCore(live, toolName, args) {
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
// run). Never touches the bridge or spawns Unity.
|
|
278
|
-
if (toolName === "unity_open_mcp_read_compile_errors") {
|
|
279
|
-
return this.routeReadCompileErrors(args);
|
|
280
|
-
}
|
|
281
|
-
// M13 T4.4 — bridge event stream pull. The MCP server is the only long-lived
|
|
282
|
-
// hop between the bridge and the agent; a per-process SSE reader amortizes
|
|
283
|
-
// the connection and shares the queue across pulls. Lives only when the
|
|
284
|
-
// bridge is reachable; an unreachable bridge returns a clear error instead
|
|
285
|
-
// of hanging on connect.
|
|
286
|
-
if (toolName === "unity_senses_pull_events") {
|
|
287
|
-
return this.routePullEvents(args, live);
|
|
288
|
-
}
|
|
289
|
-
// capabilities — local capability-discovery surface (no live/batch hop).
|
|
290
|
-
if (toolName === "unity_open_mcp_capabilities") {
|
|
291
|
-
return this.routeCapabilities(args, live);
|
|
292
|
-
}
|
|
293
|
-
// list_rules — local rule catalog (no live/batch hop). Lets an agent
|
|
294
|
-
// discover which rules apply to which asset kinds before calling
|
|
295
|
-
// scan_paths / validate_edit.
|
|
296
|
-
if (toolName === "unity_open_mcp_list_rules") {
|
|
297
|
-
return this.routeListRules(args);
|
|
298
|
-
}
|
|
299
|
-
// generate_skill — local skill generation (no live/batch hop).
|
|
300
|
-
if (toolName === "unity_open_mcp_generate_skill") {
|
|
301
|
-
return this.routeGenerateSkill(args);
|
|
302
|
-
}
|
|
303
|
-
// M18 Plan 2 / T18.2.2 — manage_tools is server-only. It mutates the
|
|
304
|
-
// per-session tool-group visibility state that lives in the MCP server
|
|
305
|
-
// (ToolSessionState). The bridge does not track session state — it answers
|
|
306
|
-
// manage_tools meta-calls by reporting the compiled tool set; the MCP
|
|
307
|
-
// server applies the activate / deactivate filter to ListTools. Always
|
|
308
|
-
// visible regardless of the current active set (meta-tool).
|
|
309
|
-
if (toolName === "unity_open_mcp_manage_tools") {
|
|
310
|
-
return this.routeManageTools(args, live);
|
|
311
|
-
}
|
|
312
|
-
// testsuite-tauri phase-3 — bridge_status. Operator-only health snapshot.
|
|
313
|
-
// Server-resolved (classifyInstance + one /ping via the LiveClient); never
|
|
314
|
-
// hits the bridge tool endpoint and never spawns Unity. Returns a coarse
|
|
315
|
-
// `status` token (running/compiling/stopped/dead_bridge) the Validation
|
|
316
|
-
// Suite app drives its offline-scenario gate off. Read-only, gate-free.
|
|
317
|
-
if (toolName === "unity_open_mcp_bridge_status") {
|
|
318
|
-
return this.routeBridgeStatus(args, live);
|
|
319
|
-
}
|
|
320
|
-
// M26 Plan 2 — Unity Hub control tools. Local-routed: resolved inside the
|
|
321
|
-
// MCP server (filesystem discovery + Unity archive feed + unityhub:// deep
|
|
322
|
-
// link + Hub CLI). Never hit the bridge tool endpoint, never spawn Unity.
|
|
323
|
-
// Mutating members are system-level ops (paths_hint N/A, gate-free).
|
|
324
|
-
if (toolName === "unity_open_mcp_hub_list_editors") {
|
|
325
|
-
return this.routeHubListEditors();
|
|
326
|
-
}
|
|
327
|
-
if (toolName === "unity_open_mcp_hub_available_releases") {
|
|
328
|
-
return this.routeHubAvailableReleases();
|
|
329
|
-
}
|
|
330
|
-
if (toolName === "unity_open_mcp_hub_install_editor") {
|
|
331
|
-
return this.routeHubInstallEditor(args);
|
|
332
|
-
}
|
|
333
|
-
if (toolName === "unity_open_mcp_hub_install_modules") {
|
|
334
|
-
return this.routeHubInstallModules(args);
|
|
335
|
-
}
|
|
336
|
-
if (toolName === "unity_open_mcp_hub_get_install_path") {
|
|
337
|
-
return this.routeHubGetInstallPath();
|
|
338
|
-
}
|
|
339
|
-
if (toolName === "unity_open_mcp_hub_set_install_path") {
|
|
340
|
-
return this.routeHubSetInstallPath(args);
|
|
341
|
-
}
|
|
342
|
-
// find_references — offline-first when no bridge is connected; live
|
|
343
|
-
// ReferenceGraph remains available when the bridge is up.
|
|
344
|
-
if (toolName === "unity_open_mcp_find_references") {
|
|
345
|
-
return this.routeFindReferences(args, live);
|
|
346
|
-
}
|
|
347
|
-
// dependencies — offline-first (M24 Plan 2 / T24.2). Forward + reverse
|
|
348
|
-
// edges + impact analysis all compute from YAML on disk when no bridge is
|
|
349
|
-
// connected; the live DependenciesTool remains available when the bridge
|
|
350
|
-
// is up. The offline path is the only one that exposes include_impact
|
|
351
|
-
// (transitive reverse closure) today.
|
|
352
|
-
if (toolName === "unity_open_mcp_dependencies") {
|
|
353
|
-
return this.routeDependencies(args, live);
|
|
354
|
-
}
|
|
355
|
-
// M22 — verify tools. The bridge returns the full issues[] list; we fold +
|
|
356
|
-
// page server-side per the output profile (compact = counts only).
|
|
357
|
-
if (toolName === "unity_open_mcp_validate_edit" || toolName === "unity_open_mcp_scan_paths") {
|
|
358
|
-
return this.routeVerifyResult(toolName, args, live);
|
|
359
|
-
}
|
|
360
|
-
// M22 — scene_get_data. Resolves the output profile (onto detail) and pages
|
|
361
|
-
// the node stream server-side; the bridge detail/max_nodes params keep
|
|
362
|
-
// working as back-compat aliases.
|
|
363
|
-
if (toolName === "unity_open_mcp_scene_get_data") {
|
|
364
|
-
return this.routeSceneGetData(args, live);
|
|
347
|
+
// Named-tool dispatch (T3.2): the handler map covers every exact-name
|
|
348
|
+
// route. The per-tool policy rationale (offline / local / hub / offline-
|
|
349
|
+
// first / verify-fold / scene-paging) lives in each routeXxx method's
|
|
350
|
+
// header comment.
|
|
351
|
+
const handler = this.getRouteHandlers().get(toolName);
|
|
352
|
+
if (handler) {
|
|
353
|
+
return handler(live, args);
|
|
365
354
|
}
|
|
366
355
|
// Compact drill-down reads: offline-first for text-serialized assets, fall
|
|
367
356
|
// back to live bridge for binary formats. The compressible-router handles
|
|
@@ -377,12 +366,27 @@ export class ToolRouter {
|
|
|
377
366
|
// running it against a live Editor that already compiled successfully
|
|
378
367
|
// would trivially report "compile_passed" and never surface the broken
|
|
379
368
|
// state the tool exists to diagnose.
|
|
380
|
-
|
|
381
|
-
|
|
369
|
+
//
|
|
370
|
+
// Verify-family tools (scan_all / baseline_create / regression_check) are
|
|
371
|
+
// batch-only: they require the headless verify package and are NOT
|
|
372
|
+
// registered on the live bridge. Routing them live (the previous default
|
|
373
|
+
// when the bridge was up) yielded a bare `tool_not_found`. An open Editor
|
|
374
|
+
// holding the project lock surfaces as `editor_instance_locked` from the
|
|
375
|
+
// batch spawn (the expected degraded outcome — agents should read the lock
|
|
376
|
+
// code, not chase a live registration miss).
|
|
377
|
+
//
|
|
378
|
+
// validate_edit / scan_paths are intentionally excluded: those ARE
|
|
379
|
+
// registered on the live bridge and stay live-first (routeVerifyResult
|
|
380
|
+
// below handles their output-profile folding). The always-batch policy is
|
|
381
|
+
// declared once in ALWAYS_BATCH_TOOLS (tool name → reason); do not add a
|
|
382
|
+
// second pinned branch for a new always-batch tool — extend the map.
|
|
383
|
+
const alwaysBatchReason = ALWAYS_BATCH_TOOLS.get(toolName);
|
|
384
|
+
if (alwaysBatchReason !== undefined) {
|
|
385
|
+
console.error(`[unity-open-mcp] Route: ${toolName} -> batch (always-batch: ${alwaysBatchReason})`);
|
|
382
386
|
const result = await this.batch.route(toolName, args);
|
|
383
387
|
return injectRouteMeta(result, {
|
|
384
388
|
route: "batch",
|
|
385
|
-
fallbackReason:
|
|
389
|
+
fallbackReason: alwaysBatchReason,
|
|
386
390
|
});
|
|
387
391
|
}
|
|
388
392
|
if (!canBatch && !isPing) {
|
|
@@ -500,38 +504,29 @@ export class ToolRouter {
|
|
|
500
504
|
if (stale.staleLogSuspected)
|
|
501
505
|
staleLog = stale;
|
|
502
506
|
}
|
|
503
|
-
return {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
staleLogNewerFiles: staleLog.newerFiles,
|
|
527
|
-
}
|
|
528
|
-
: {}),
|
|
529
|
-
_source: "offline",
|
|
530
|
-
}),
|
|
531
|
-
},
|
|
532
|
-
],
|
|
533
|
-
isError: false,
|
|
534
|
-
};
|
|
507
|
+
return sourceResult({
|
|
508
|
+
status,
|
|
509
|
+
unhealthy: health.unhealthy,
|
|
510
|
+
headline: health.headline,
|
|
511
|
+
errorCount: errors.length,
|
|
512
|
+
errors,
|
|
513
|
+
// Package / assembly issues from the same log tail. Empty when the
|
|
514
|
+
// only red flags are compiler errors (the common case).
|
|
515
|
+
issues: health.issues,
|
|
516
|
+
issueCount: health.issues.length,
|
|
517
|
+
logPath,
|
|
518
|
+
tailBytes: tail.bytes,
|
|
519
|
+
// Present ONLY when at least one cited source file is newer than
|
|
520
|
+
// Editor.log — the log's most-recent error block may be stale and
|
|
521
|
+
// the agent should force a recompile before trusting the errors.
|
|
522
|
+
...(staleLog
|
|
523
|
+
? {
|
|
524
|
+
staleLogSuspected: true,
|
|
525
|
+
staleLogHint: staleLog.hint,
|
|
526
|
+
staleLogNewerFiles: staleLog.newerFiles,
|
|
527
|
+
}
|
|
528
|
+
: {}),
|
|
529
|
+
}, "offline");
|
|
535
530
|
}
|
|
536
531
|
async routeListAssets(args) {
|
|
537
532
|
try {
|
|
@@ -541,10 +536,7 @@ export class ToolRouter {
|
|
|
541
536
|
maxPerFolder: typeof args.max_per_folder === "number" ? args.max_per_folder : 30,
|
|
542
537
|
projectRoot: this.projectPath,
|
|
543
538
|
});
|
|
544
|
-
return
|
|
545
|
-
content: [{ type: "text", text: JSON.stringify({ ...result, _source: "offline" }) }],
|
|
546
|
-
isError: false,
|
|
547
|
-
};
|
|
539
|
+
return sourceResult(result, "offline");
|
|
548
540
|
}
|
|
549
541
|
catch (err) {
|
|
550
542
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -576,7 +568,7 @@ export class ToolRouter {
|
|
|
576
568
|
"Ensure the Unity Editor is open with the Agent Bridge running. " +
|
|
577
569
|
"The bridge port is per-project (20000 + sha256(projectPath) % 10000), not fixed — " +
|
|
578
570
|
`if Unity is open, check the instance lock at ${lockPath(this.projectPath)} ` +
|
|
579
|
-
|
|
571
|
+
`for the live port/pid, or set ${PORT_ENV_VAR}.`,
|
|
580
572
|
},
|
|
581
573
|
}),
|
|
582
574
|
},
|
|
@@ -623,24 +615,14 @@ export class ToolRouter {
|
|
|
623
615
|
fixes: FIX_CATALOG,
|
|
624
616
|
availableBridgeTools,
|
|
625
617
|
}, { kind, includePlanned });
|
|
626
|
-
return
|
|
627
|
-
content: [
|
|
628
|
-
{ type: "text", text: JSON.stringify({ ...result, _source: "local" }) },
|
|
629
|
-
],
|
|
630
|
-
isError: false,
|
|
631
|
-
};
|
|
618
|
+
return sourceResult(result, "local");
|
|
632
619
|
}
|
|
633
620
|
async routeListRules(args) {
|
|
634
621
|
const assetKind = typeof args.asset_kind === "string" ? args.asset_kind : undefined;
|
|
635
622
|
const extension = typeof args.extension === "string" ? args.extension : undefined;
|
|
636
623
|
const implementedOnly = args.implemented_only === true;
|
|
637
624
|
const result = listRules({ rules: RULE_CATALOG, fixes: FIX_CATALOG }, { assetKind, extension, implementedOnly });
|
|
638
|
-
return
|
|
639
|
-
content: [
|
|
640
|
-
{ type: "text", text: JSON.stringify({ ...result, _source: "local" }) },
|
|
641
|
-
],
|
|
642
|
-
isError: false,
|
|
643
|
-
};
|
|
625
|
+
return sourceResult(result, "local");
|
|
644
626
|
}
|
|
645
627
|
async routeGenerateSkill(args) {
|
|
646
628
|
const write = args.write === true;
|
|
@@ -664,20 +646,11 @@ export class ToolRouter {
|
|
|
664
646
|
clients: clients.length > 0 ? clients : undefined,
|
|
665
647
|
includeWorkflow,
|
|
666
648
|
});
|
|
667
|
-
return {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
skill: result.skill,
|
|
673
|
-
project: result.project,
|
|
674
|
-
written: result.written,
|
|
675
|
-
_source: "local",
|
|
676
|
-
}),
|
|
677
|
-
},
|
|
678
|
-
],
|
|
679
|
-
isError: false,
|
|
680
|
-
};
|
|
649
|
+
return sourceResult({
|
|
650
|
+
skill: result.skill,
|
|
651
|
+
project: result.project,
|
|
652
|
+
written: result.written,
|
|
653
|
+
}, "local");
|
|
681
654
|
}
|
|
682
655
|
catch (err) {
|
|
683
656
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -719,7 +692,7 @@ export class ToolRouter {
|
|
|
719
692
|
return this.manageToolsResult({
|
|
720
693
|
reset: true,
|
|
721
694
|
activeGroups: this.sessionState.activeGroups(),
|
|
722
|
-
message: "Tool-group visibility restored to
|
|
695
|
+
message: "Tool-group visibility restored to the five default-on groups. " +
|
|
723
696
|
"MCP clients that support listChanged will refresh ListTools automatically.",
|
|
724
697
|
});
|
|
725
698
|
}
|
|
@@ -796,26 +769,17 @@ export class ToolRouter {
|
|
|
796
769
|
tools,
|
|
797
770
|
};
|
|
798
771
|
});
|
|
799
|
-
return {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
"whether the Unity domain dependency is compiled in; use " +
|
|
811
|
-
"unity_open_mcp_capabilities for the authoritative compiled-state " +
|
|
812
|
-
"report.",
|
|
813
|
-
_source: "local",
|
|
814
|
-
}),
|
|
815
|
-
},
|
|
816
|
-
],
|
|
817
|
-
isError: false,
|
|
818
|
-
};
|
|
772
|
+
return sourceResult({
|
|
773
|
+
groups,
|
|
774
|
+
activeGroups: this.sessionState.activeGroups(),
|
|
775
|
+
note: "Activate a group to add its tools to your ListTools surface; " +
|
|
776
|
+
"deactivate to hide them. State is per-session and ephemeral — " +
|
|
777
|
+
"it resets to the five default-on groups when the MCP server restarts. " +
|
|
778
|
+
"Compiled-state availability (the `available` field) reflects " +
|
|
779
|
+
"whether the Unity domain dependency is compiled in; use " +
|
|
780
|
+
"unity_open_mcp_capabilities for the authoritative compiled-state " +
|
|
781
|
+
"report.",
|
|
782
|
+
}, "local");
|
|
819
783
|
}
|
|
820
784
|
/**
|
|
821
785
|
* Notify MCP clients when the filtered ListTools surface changed. Skips the
|
|
@@ -927,23 +891,10 @@ export class ToolRouter {
|
|
|
927
891
|
return out;
|
|
928
892
|
}
|
|
929
893
|
manageToolsResult(body) {
|
|
930
|
-
return
|
|
931
|
-
content: [
|
|
932
|
-
{ type: "text", text: JSON.stringify({ ...body, _source: "local" }) },
|
|
933
|
-
],
|
|
934
|
-
isError: false,
|
|
935
|
-
};
|
|
894
|
+
return sourceResult(body, "local");
|
|
936
895
|
}
|
|
937
896
|
manageToolsError(code, message) {
|
|
938
|
-
return {
|
|
939
|
-
content: [
|
|
940
|
-
{
|
|
941
|
-
type: "text",
|
|
942
|
-
text: JSON.stringify({ error: { code, message }, _source: "local" }),
|
|
943
|
-
},
|
|
944
|
-
],
|
|
945
|
-
isError: true,
|
|
946
|
-
};
|
|
897
|
+
return sourceResult({ error: { code, message } }, "local", true);
|
|
947
898
|
}
|
|
948
899
|
// testsuite-tauri phase-3 — bridge_status. Combines the instance-lock
|
|
949
900
|
// classifier (the same pure function the LiveClient uses for dead-bridge
|
|
@@ -1089,15 +1040,11 @@ export class ToolRouter {
|
|
|
1089
1040
|
}
|
|
1090
1041
|
: { reachable: false },
|
|
1091
1042
|
nextStep: bridgeStatusNextStep(status),
|
|
1092
|
-
_source: "local",
|
|
1093
1043
|
};
|
|
1094
1044
|
// bridge_status never reports an error — even a stopped bridge is a
|
|
1095
1045
|
// successful status read. _source=local because the synthesis happens in
|
|
1096
1046
|
// the MCP server (no bridge tool endpoint, no batch Unity).
|
|
1097
|
-
return
|
|
1098
|
-
content: [{ type: "text", text: JSON.stringify(body) }],
|
|
1099
|
-
isError: false,
|
|
1100
|
-
};
|
|
1047
|
+
return sourceResult(body, "local");
|
|
1101
1048
|
}
|
|
1102
1049
|
// ── M26 Plan 2 — Unity Hub control (local-routed) ────────────────
|
|
1103
1050
|
//
|
|
@@ -1109,29 +1056,16 @@ export class ToolRouter {
|
|
|
1109
1056
|
// level ops where the project-asset gate does not apply (paths_hint N/A).
|
|
1110
1057
|
async routeHubListEditors() {
|
|
1111
1058
|
const editors = this.hubBackend.listInstalledEditors();
|
|
1112
|
-
|
|
1113
|
-
editors,
|
|
1114
|
-
count: editors.length,
|
|
1115
|
-
_source: "local",
|
|
1116
|
-
};
|
|
1117
|
-
return {
|
|
1118
|
-
content: [{ type: "text", text: JSON.stringify(body) }],
|
|
1119
|
-
isError: false,
|
|
1120
|
-
};
|
|
1059
|
+
return sourceResult({ editors, count: editors.length }, "local");
|
|
1121
1060
|
}
|
|
1122
1061
|
async routeHubAvailableReleases() {
|
|
1123
1062
|
const result = await this.hubBackend.fetchAvailableReleases();
|
|
1124
|
-
|
|
1063
|
+
return sourceResult({
|
|
1125
1064
|
entries: result.entries,
|
|
1126
1065
|
count: result.entries.length,
|
|
1127
1066
|
stale: result.stale,
|
|
1128
1067
|
fetchedAt: result.fetchedAt,
|
|
1129
|
-
|
|
1130
|
-
};
|
|
1131
|
-
return {
|
|
1132
|
-
content: [{ type: "text", text: JSON.stringify(body) }],
|
|
1133
|
-
isError: false,
|
|
1134
|
-
};
|
|
1068
|
+
}, "local");
|
|
1135
1069
|
}
|
|
1136
1070
|
async routeHubInstallEditor(args) {
|
|
1137
1071
|
const version = typeof args.version === "string" ? args.version : "";
|
|
@@ -1192,16 +1126,7 @@ export class ToolRouter {
|
|
|
1192
1126
|
}
|
|
1193
1127
|
async routeHubGetInstallPath() {
|
|
1194
1128
|
const res = this.hubBackend.getInstallPath();
|
|
1195
|
-
|
|
1196
|
-
path: res.path,
|
|
1197
|
-
source: res.source,
|
|
1198
|
-
error: res.error,
|
|
1199
|
-
_source: "local",
|
|
1200
|
-
};
|
|
1201
|
-
return {
|
|
1202
|
-
content: [{ type: "text", text: JSON.stringify(body) }],
|
|
1203
|
-
isError: res.error !== null,
|
|
1204
|
-
};
|
|
1129
|
+
return sourceResult({ path: res.path, source: res.source, error: res.error }, "local", res.error !== null);
|
|
1205
1130
|
}
|
|
1206
1131
|
async routeHubSetInstallPath(args) {
|
|
1207
1132
|
const path = typeof args.path === "string" ? args.path : "";
|
|
@@ -1231,14 +1156,19 @@ export class ToolRouter {
|
|
|
1231
1156
|
console.error("[unity-open-mcp] Route: find_references -> live");
|
|
1232
1157
|
// The live bridge only honors max_results (no detail/per-file/threshold).
|
|
1233
1158
|
// Ask for the full set when paging so the cursor can walk it; otherwise
|
|
1234
|
-
// honor the legacy max_results cap.
|
|
1159
|
+
// honor the legacy max_results cap. The 0 here is the server-internal
|
|
1160
|
+
// "unlimited" sentinel (documented in the find-references schema); callers
|
|
1161
|
+
// never pass it — the schema declares minimum: 1 for caller-facing use.
|
|
1235
1162
|
const liveArgs = {
|
|
1236
1163
|
...args,
|
|
1237
1164
|
max_results: wantPaging ? 0 : (typeof args.max_results === "number" ? args.max_results : 100),
|
|
1238
1165
|
};
|
|
1239
1166
|
const result = await live.route("unity_open_mcp_find_references", liveArgs);
|
|
1240
1167
|
const folded = foldFindReferencesLive(result, profile, wantPaging, args);
|
|
1241
|
-
|
|
1168
|
+
// T6.1 — tag _source=live so the response matches the offline path's
|
|
1169
|
+
// _source=offline tag (clients switching on _source get a defined value
|
|
1170
|
+
// for the common live case instead of undefined).
|
|
1171
|
+
return injectRouteMeta(tagSource(folded, "live"), { route: "live" });
|
|
1242
1172
|
}
|
|
1243
1173
|
console.error("[unity-open-mcp] Route: find_references -> offline (live bridge unavailable)");
|
|
1244
1174
|
const assetPath = typeof args.asset_path === "string" ? args.asset_path : undefined;
|
|
@@ -1265,6 +1195,8 @@ export class ToolRouter {
|
|
|
1265
1195
|
guid,
|
|
1266
1196
|
detail,
|
|
1267
1197
|
// When paging, fetch the full set; otherwise honor the legacy cap.
|
|
1198
|
+
// 0 is the server-internal "unlimited" sentinel (see find-references
|
|
1199
|
+
// schema description); the schema's caller-facing minimum is 1.
|
|
1268
1200
|
maxResults: wantPaging ? 0 : (typeof args.max_results === "number" ? args.max_results : 100),
|
|
1269
1201
|
maxPerFile: typeof args.max_per_file === "number" ? args.max_per_file : 5,
|
|
1270
1202
|
patternThreshold: typeof args.pattern_threshold === "number" ? args.pattern_threshold : 0,
|
|
@@ -1275,12 +1207,7 @@ export class ToolRouter {
|
|
|
1275
1207
|
const withPaging = wantPaging
|
|
1276
1208
|
? pageFindReferencesResult(result, args.page_size, typeof args.cursor === "string" ? args.cursor : undefined)
|
|
1277
1209
|
: result;
|
|
1278
|
-
return
|
|
1279
|
-
content: [
|
|
1280
|
-
{ type: "text", text: JSON.stringify({ ...withPaging, _source: "offline" }) },
|
|
1281
|
-
],
|
|
1282
|
-
isError: false,
|
|
1283
|
-
};
|
|
1210
|
+
return sourceResult(withPaging, "offline");
|
|
1284
1211
|
}
|
|
1285
1212
|
catch (err) {
|
|
1286
1213
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1338,7 +1265,8 @@ export class ToolRouter {
|
|
|
1338
1265
|
max_results: typeof args.max_results === "number" ? args.max_results : 100,
|
|
1339
1266
|
};
|
|
1340
1267
|
const result = await live.route("unity_open_mcp_dependencies", liveArgs);
|
|
1341
|
-
|
|
1268
|
+
// T6.1 — tag _source=live for parity with the offline route's _source.
|
|
1269
|
+
return injectRouteMeta(tagSource(result, "live"), { route: "live" });
|
|
1342
1270
|
}
|
|
1343
1271
|
console.error(includeImpact && liveAvailable
|
|
1344
1272
|
? "[unity-open-mcp] Route: dependencies -> offline (include_impact is offline-only)"
|