A static catalog of editor capabilities for the s&box Editor. It defines a record type SboxCapabilityMetadata, constructs a hardcoded list of capability entries with metadata (tool id, category, summary, access mode, required states, verification, keywords), and provides lookup/search utilities (All, Find, Search) with scoring logic.
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
namespace Editor.Mcp
{
internal sealed record SboxCapabilityMetadata(
string Tool,
string Category,
string Summary,
string AccessMode,
bool RequiresApproval,
string[] RequiredEditorStates,
string Verification,
string[] Keywords)
{
public object ToPayload() => new
{
Tool,
Category,
Summary,
AccessMode,
RequiresApproval,
RequiredEditorStates,
Verification,
Keywords
};
}
internal static class SboxCapabilityCatalog
{
private static readonly IReadOnlyList<SboxCapabilityMetadata> Items = new[]
{
Read("pocketknife_find_capability", "discovery", "Find a small ranked set of editor tools by task terms before loading a tool schema.", Array.Empty<string>(), "Results are ranked from explicit metadata and do not execute a tool.", "capability", "search", "routing", "tool"),
Read("pocketknife_query_schema", "api", "Search the official s&box API schema for types and members.", new[] { "editor-process" }, "The response identifies the official schema source and cache timestamp.", "sbox", "api", "schema", "type"),
Read("pocketknife_search_local_references", "reference", "Search the approved local reference index for code, systems, assets, and documentation.", new[] { "editor-process" }, "Results include bounded snippets and indexed source metadata.", "reference", "vault", "code", "asset", "system"),
Write("pocketknife_rebuild_local_reference_index", "reference", "Rebuild the local reference index from its configured catalog.", new[] { "editor-process" }, "The index reports the scanned source set and resulting entry count.", "reference", "index", "rebuild"),
Read("pocketknife_get_local_reference", "reference", "Read one bounded local reference result by stable ID.", new[] { "editor-process" }, "The returned item is resolved from the local reference index.", "reference", "read", "source"),
Read("pocketknife_search_live_codesearch", "api", "Search public s&box Code Search results through the official page or local worker.", new[] { "editor-process" }, "The response retains official package/file URLs and transport status.", "sbox", "code", "search", "public"),
Analyze("pocketknife_test_clothing", "asset", "Load a clothing asset into the configured test harness for inspection.", new[] { "editor-process", "active-project" }, "The result reports the selected asset and test-harness outcome.", "clothing", "avatar", "asset"),
External("pocketknife_open_publish_wizard", "publishing", "Open the s&box publishing workflow for the active project.", new[] { "editor-process", "active-project" }, "The editor acceptance is reported; publication itself requires a separate explicit workflow.", "publish", "external", "package"),
Write("pocketknife_spawn_procedural_road", "procedural", "Create a project road component using the loaded s&box runtime types.", new[] { "editor-process", "active-scene" }, "The created object and component type are returned.", "road", "procedural", "scene"),
Write("pocketknife_spawn_water_surface", "procedural", "Create a project water surface component using the loaded runtime types.", new[] { "editor-process", "active-scene" }, "The created object and component type are returned.", "water", "procedural", "scene"),
Write("pocketknife_spawn_architecture_plan", "procedural", "Create a bounded architecture plan in the active scene.", new[] { "editor-process", "active-scene" }, "The plan object and requested dimensions are returned.", "architecture", "building", "procedural"),
Write("pocketknife_add_architecture_room", "procedural", "Add a room to an existing architecture plan.", new[] { "editor-process", "active-scene" }, "The parent, room, position, and dimensions are returned.", "architecture", "room", "procedural"),
Write("pocketknife_navmesh_bake", "navigation", "Bake the active scene navigation mesh when its dependency is available.", new[] { "editor-process", "active-scene" }, "The navigation mesh leaves its generating/dirty state before success is reported.", "navmesh", "navigation", "bake"),
Write("pocketknife_switch_editor_tab", "editor", "Open or activate a named editor surface when its state can be verified.", new[] { "editor-process" }, "The matching surface or dock activation is checked after the request.", "editor", "tab", "surface"),
Read("pocketknife_dock_state", "editor", "List bounded editor dock state including open, active, and raised readback.", new[] { "editor-process" }, "Dock identity and state are returned from Editor.DockManager.", "editor", "dock", "state"),
Write("pocketknife_configure_navmesh", "navigation", "Enable or disable the active scene navigation mesh and optionally save it.", new[] { "editor-process", "active-scene" }, "The requested enabled state and save result are read back.", "navmesh", "navigation", "configure"),
Analyze("pocketknife_find_type", "code", "Find loaded s&box types by full name, short name, or fragment.", new[] { "editor-process" }, "The result lists matching loaded types and a bounded count.", "code", "type", "reflection"),
Analyze("pocketknife_type_members", "code", "Inspect properties and methods for one loaded s&box type.", new[] { "editor-process" }, "The returned member list is derived from loaded runtime assemblies.", "code", "type", "members"),
Write("pocketknife_compile_await", "build", "Await project compiler freshness after a compile dispatch.", new[] { "editor-process", "active-project" }, "Baseline and observed compiler revisions plus settled diagnostics are returned.", "compile", "build", "freshness"),
Read("pocketknife_package_references", "project", "Read package references from the active project's .sbproj.", new[] { "editor-process", "active-project" }, "The selected project file and parsed package references are returned.", "project", "packages", "dependencies"),
Write("pocketknife_open_in_hammer", "asset", "Open a .vmap asset in the Hammer editor when surface state is verifiable.", new[] { "editor-process", "active-project" }, "The selected asset path and active-surface verification state are returned.", "hammer", "vmap", "editor"),
Write("pocketknife_edit_material", "asset", "Open a .vmat asset in the material editor when surface state is verifiable.", new[] { "editor-process", "active-project" }, "The selected material path and active-surface verification state are returned.", "material", "vmat", "editor"),
Write("pocketknife_create_sound_event", "asset", "Create a bounded .sound event asset from an approved audio path.", new[] { "editor-process", "active-project" }, "The asset path and editor refresh result are verified.", "sound", "audio", "asset"),
Read("pocketknife_asset_search", "asset", "Search the editor asset index with bounded kind and path filters.", new[] { "editor-process", "active-project" }, "Results include count, pagination, source, and provenance.", "asset", "search", "project"),
Write("pocketknife_paint_terrain", "scene", "Create the supported terrain component for a bounded terrain request.", new[] { "editor-process", "active-scene" }, "The created component is returned; unsupported paint APIs remain unavailable.", "terrain", "scene", "component"),
Write("pocketknife_spawn_sandbox_entity", "scene", "Create a supported sandbox prop entity from a project model asset.", new[] { "editor-process", "active-scene" }, "The object, renderer, collider, and physics state are returned.", "entity", "prop", "physics"),
Read("pocketknife_scene_sessions", "editor", "List open scene sessions with stable IDs and lifecycle state.", new[] { "editor-process" }, "Session ID, path, active, dirty, mounted, and playing state are returned.", "scene", "session", "tabs"),
Write("pocketknife_scene_activate", "editor", "Activate an existing scene session by stable ID or path.", new[] { "editor-process" }, "SceneEditorSession.Active is read back before success.", "scene", "session", "activate"),
Analyze("pocketknife_viewport_capture", "observability", "Capture an allowlisted editor or scene camera mode with verified PNG output.", new[] { "editor-process" }, "Mode, dimensions, bytes, file, and image block are verified.", "screenshot", "viewport", "camera"),
Read("pocketknife_package_search", "package", "Search published packages with bounded provenance-aware results.", new[] { "editor-process" }, "Package ident, title, author, type, source, and provenance are returned.", "package", "cloud", "search"),
Write("pocketknife_package_install", "package", "Install one published package through the typed editor asset system.", new[] { "editor-process", "active-project" }, "Installed revision and dependency state are read back.", "package", "cloud", "install"),
Read("pocketknife_package_status", "package", "Read cloud package metadata and local installed revision state.", new[] { "editor-process" }, "Installed state and revision are returned without exposing credentials.", "package", "cloud", "status"),
Read("pocketknife_asset_info", "asset", "Read indexed asset or package provenance and compile state.", new[] { "editor-process" }, "Resource path, source, package, revision, compile state, and surface are returned.", "asset", "info", "provenance"),
Read("pocketknife_editor_surface_status", "editor", "Report runtime availability and dependencies for an allowlisted editor surface.", new[] { "editor-process" }, "Runtime type, official matches, and missing dependency code are returned.", "surface", "editor", "dependency"),
Write("pocketknife_editor_document_open", "editor", "Open an allowlisted editor document only when active-surface state can be checked.", new[] { "editor-process", "active-project" }, "OpenFile dispatch is not Ready without surface readback.", "document", "surface", "open"),
Read("pocketknife_navmesh_status", "navigation", "Read typed NavMesh state for an open scene session.", new[] { "editor-process", "active-scene" }, "Enabled, generating, dirty, scene, and dependency state are returned.", "navmesh", "navigation", "status"),
Read("pocketknife_navmesh_path", "navigation", "Calculate a bounded path through the active typed NavMesh.", new[] { "editor-process", "active-scene" }, "Returned points are capped and tied to the active scene.", "navmesh", "navigation", "path"),
Read("pocketknife_network_status", "network", "Read redacted typed editor and provider availability state.", new[] { "editor-process" }, "No credentials or unrestricted endpoints are returned.", "network", "status", "redacted"),
Write("pocketknife_ui_interact", "synthetic-player", "Simulate UI interactions like click, mousedown, mouseup, hover, or text entry on runtime panels.", new[] { "editor-process", "play-mode" }, "The targeted UI panel, rect, and action outcome are verified.", "ui", "synthetic", "click", "interact", "player"),
Write("pocketknife_player_simulate", "synthetic-player", "Simulate player movement actions and button inputs in active play mode.", new[] { "editor-process", "play-mode" }, "The simulated action, duration, and execution status are verified.", "player", "simulate", "input", "movement", "action"),
Write("pocketknife_aim_simulate", "synthetic-player", "Simulate player look angles or target aiming in active play mode.", new[] { "editor-process", "play-mode" }, "Old and new eye angles or ray direction are returned.", "aim", "look", "camera", "simulate", "player"),
Write("pocketknife_video_record", "diagnostics", "Start, stop, or query MP4 video recording of editor or game sessions.", new[] { "editor-process" }, "Recording state and target filename are returned.", "video", "record", "mp4", "diagnostics"),
Write("pocketknife_network_condition", "diagnostics", "Adjust simulated latency, packet loss, or host network connectivity.", new[] { "editor-process" }, "Network condition settings and hosting state are returned.", "network", "lag", "packetloss", "diagnostics", "latency"),
Write("pocketknife_voice_diagnostic", "diagnostics", "Start, stop, save, or query voice recording diagnostics.", new[] { "editor-process" }, "Voice recording status and destination file path are returned.", "voice", "audio", "mic", "diagnostics", "record"),
Write("pocketknife_chat_dispatch", "diagnostics", "Dispatch a chat message as a system event or player chat.", new[] { "editor-process", "play-mode" }, "Message delivery and dispatch mode are returned.", "chat", "message", "dispatch", "diagnostics"),
Read("pocketknife_play_inspect", "diagnostics", "Inspect runtime GameObject and component state in active play mode.", new[] { "editor-process", "play-mode" }, "GameObject identity, position, and components are returned.", "inspect", "gameobject", "runtime", "diagnostics"),
Write("pocketknife_run_test_scenario", "diagnostics", "Execute an automated multi-step test scenario with optional video recording.", new[] { "editor-process", "play-mode" }, "Scenario pass/fail status, executed steps, and failures are returned.", "scenario", "test", "automated", "runner", "diagnostics"),
Write("pocketknife_terrain_sculpt", "terrain", "Procedurally sculpt or flatten terrain heightmaps using noise algorithms.", new[] { "editor-process", "active-scene" }, "The targeted terrain, resolution, algorithm, and height range are returned.", "terrain", "sculpt", "procedural", "heightmap", "noise"),
Write("pocketknife_terrain_paint", "terrain", "Paint terrain splat materials by dispatching compute-shader texture updates at UV coordinates.", new[] { "editor-process", "active-scene" }, "The targeted terrain, UV coordinates, splat channel, and brush parameters are returned.", "terrain", "paint", "splat", "material", "texture", "brush"),
Write("pocketknife_mesh_generate", "world-building", "Generate a procedural 3D model mesh and attach it to a scene GameObject with optional collider.", new[] { "editor-process", "active-scene" }, "The generated mesh GameObject name, id, dimensions, and collider state are returned.", "mesh", "procedural", "model", "geometry", "box", "shape"),
Write("pocketknife_mapping_primitive", "world-building", "Create an editable mapping brush primitive entity in the active scene.", new[] { "editor-process", "active-scene" }, "The created mapping brush primitive GameObject name and extents are returned.", "mapping", "primitive", "brush", "hammer", "block", "wedge"),
Write("pocketknife_clutter_scatter", "world-building", "Scatter instanced clutter and vegetation across the scene using ClutterGridSystem.", new[] { "editor-process", "active-scene" }, "Clutter grid status and action outcomes are returned.", "clutter", "scatter", "vegetation", "instances", "foliage", "density"),
Write("pocketknife_physics_settle", "world-building", "Simulate in-editor physics to settle rigidbodies naturally into resting positions.", new[] { "editor-process", "active-scene" }, "Settled rigidbodies count, duration, and resting positions state are returned.", "physics", "settle", "rigidbody", "simulation", "resting"),
Write("pocketknife_environment_tune", "environment", "Tune scene sun position, sun color, and fog density.", new[] { "editor-process", "active-scene" }, "Sun and fog configuration outcomes are returned.", "environment", "sun", "fog", "sky", "atmosphere"),
Write("pocketknife_lighting_setup", "lighting", "Create and configure directional, point, or spot lights.", new[] { "editor-process", "active-scene" }, "Created light GameObject and type are returned.", "lighting", "light", "directional", "point", "spot"),
Write("pocketknife_audio_setup", "audio", "Create and configure sound point or audio components in the active scene.", new[] { "editor-process", "active-scene" }, "Created audio GameObject and sound asset information are returned.", "audio", "sound", "soundpoint", "spatial"),
Write("pocketknife_postprocess_tune", "postprocess", "Configure and tune post-processing effects such as bloom or vignette.", new[] { "editor-process", "active-scene" }, "Post-process effect status and intensity are returned.", "postprocess", "bloom", "vignette", "camera", "effects"),
Write("pocketknife_clothing_manage", "clothing", "Manage clothing items equipped to an avatar GameObject.", new[] { "editor-process", "active-scene" }, "Avatar clothing management outcomes are returned.", "clothing", "avatar", "character", "equip"),
Write("pocketknife_material_compose", "material", "Compose and write a new shader-based .vmat material asset.", new[] { "editor-process", "active-project" }, "Written material asset path and status are returned.", "material", "vmat", "shader", "compose"),
Write("pocketknife_physics_rig", "physics", "Configure procedural physics rigs such as vehicle chassis or ragdolls.", new[] { "editor-process", "active-scene" }, "Rigging configuration outcomes are returned.", "physics", "rig", "vehicle", "chassis"),
Write("pocketknife_asset_quick_place", "workflow", "Search for models/prefabs and quick-place placeholder assets into the scene.", new[] { "editor-process", "active-scene" }, "Spawned asset information, coordinates, and selection status are returned.", "asset", "quickplace", "place", "model", "prefab", "spawn"),
Write("pocketknife_editor_select", "workflow", "Synchronize editor selection to target GameObject and focus camera.", new[] { "editor-process", "active-scene" }, "Selected GameObject details and camera focus confirmation are returned.", "select", "selection", "editor", "focus", "gameobject"),
Write("pocketknife_viewport_display", "viewport", "Configure viewport render debug mode, projection view, wireframe, grid, and post-processing.", new[] { "editor-process" }, "Configured viewport display state is returned.", "viewport", "render", "wireframe", "grid", "camera", "perspective", "shading"),
Write("pocketknife_multiplayer_orchestrate", "multiplayer", "Orchestrate local multiplayer instances, dedicated server launch, and host migration.", new[] { "editor-process" }, "Multiplayer action status and active instance counts are returned.", "multiplayer", "instances", "dedicated", "server", "host", "migration"),
Analyze("pocketknife_asset_orbit_snapshot", "observability", "Capture a 4-angle visual orbit snapshot preview around a target GameObject.", new[] { "editor-process", "active-scene" }, "Target name, resolution, output artifact path, and viewpoints are returned.", "orbit", "snapshot", "preview", "camera", "asset", "viewport", "viewpoints"),
Write("pocketknife_project_settings", "settings", "Inspect and update multi-config project settings files.", new[] { "editor-process", "active-project" }, "Project settings action outcome, configuration name, and content are returned.", "settings", "project", "config", "platform", "collision"),
Write("pocketknife_sprite_manage", "asset", "Inspect or create .sprite assets with animations and frame settings.", new[] { "editor-process", "active-project" }, "Sprite path, creation status, and frame parameters are returned.", "sprite", "2d", "animation", "asset"),
Write("pocketknife_shadergraph_manage", "shader", "Inspect or create ShaderGraph template assets for lit, unlit, postprocess, or ui shaders.", new[] { "editor-process", "active-project" }, "ShaderGraph path, template type, and creation status are returned.", "shadergraph", "shader", "material", "graph", "asset"),
Write("pocketknife_hotspot_manage", "texture", "Inspect, create, or apply .rect hotspot texture mapping assets to meshes.", new[] { "editor-process", "active-project" }, "Rect hotspot path, creation status, and target mesh are returned.", "hotspot", "rect", "texture", "mesh", "uv", "asset"),
External("pocketknife_publish_package", "publishing", "Inspect manifest, preflight, or publish the active package to sbox.game.", new[] { "editor-process", "active-project" }, "Publish outcome, manifest inspection details, and change details are returned.", "publish", "package", "sbox", "cloud", "upload", "manifest"),
Write("pocketknife_scene_save", "scene", "Headlessly serialize and save the active scene to disk without modal prompts.", new[] { "editor-process", "active-scene" }, "Saved file path, byte size, and headless confirmation are returned.", "scene", "save", "serialize", "headless"),
Write("pocketknife_scene_cleanup", "scene", "Purge temporary test objects, hazard clutter, and orphaned cameras from the active scene.", new[] { "editor-process", "active-scene" }, "Purged object count, names, IDs, and positions are returned.", "cleanup", "purge", "scene", "temporary", "test"),
Write("pocketknife_collision_matrix", "physics", "Query, inspect, and update project collision rules and tags in Collision.config.", new[] { "editor-process", "active-project" }, "Collision matrix, tag list, and updated pair rules are returned.", "collision", "matrix", "tags", "physics", "rules", "layers"),
Write("pocketknife_audio_mixer_manage", "audio", "Query and adjust audio mixer bus volumes, ducking factors, and mutes across the scene.", new[] { "editor-process", "active-scene" }, "Mixer buses, active source counts, and applied volume adjustments are returned.", "audio", "mixer", "bus", "volume", "ducking", "mute", "sound"),
Analyze("pocketknife_asset_dependencies", "asset", "Scan a project asset and build a complete dependency tree verifying references on disk.", new[] { "editor-process", "active-project" }, "Referenced materials, textures, models, sounds, and missing items are returned.", "asset", "dependency", "tree", "integrity", "references"),
Write("pocketknife_camera_navigate", "camera", "Navigate the scene editor camera or inspect active camera transform and angles.", new[] { "editor-process", "active-scene" }, "Active camera position, angles, and move outcome are returned.", "camera", "navigate", "transform", "angles", "position", "lookat"),
Analyze("pocketknife_multiview_snapshot", "observability", "Capture a 4-view isometric/orthographic viewport snapshot grid.", new[] { "editor-process", "active-scene" }, "Layout, resolution, and output image artifact path are returned.", "multiview", "snapshot", "ortho", "isometric", "quad", "viewport"),
Write("pocketknife_chat_simulate", "diagnostics", "Simulate in-game text chat messages or announcements across channels.", new[] { "editor-process" }, "Sent status, channel, and platform chat confirmation are returned.", "chat", "message", "say", "announcement", "channel"),
Analyze("pocketknife_voice_diagnostics", "audio", "Query active spatial voice stream components and audio listeners.", new[] { "editor-process", "active-scene" }, "Voice stream count and audio listener state are returned.", "voice", "spatial", "audio", "diagnostics", "stream")
};
public static IReadOnlyList<SboxCapabilityMetadata> All => Items;
public static SboxCapabilityMetadata? Find(string tool)
=> Items.FirstOrDefault(item => item.Tool.Equals(tool?.Trim(), StringComparison.OrdinalIgnoreCase));
public static IReadOnlyList<SboxCapabilityMetadata> Search(string query, int limit, int offset, out int total)
{
var normalized = (query ?? "").Trim().ToLowerInvariant();
var terms = normalized.Split(new[] { ' ', '\t', '\r', '\n', '/', '\\', '.', ',', ':', ';', '-', '_' }, StringSplitOptions.RemoveEmptyEntries).Where(term => term.Length >= 2).ToArray();
var ranked = Items
.Select(item => new { Item = item, Score = Score(item, normalized, terms) })
.Where(result => terms.Length == 0 || result.Score > 0)
.OrderByDescending(result => result.Score)
.ThenBy(result => result.Item.Tool, StringComparer.OrdinalIgnoreCase)
.ToArray();
total = ranked.Length;
return ranked.Skip(Math.Max(offset, 0)).Take(Math.Clamp(limit, 1, 20)).Select(result => result.Item).ToArray();
}
private static int Score(SboxCapabilityMetadata item, string query, string[] terms)
{
if ( terms.Length == 0 ) return 1;
var tool = item.Tool.ToLowerInvariant();
var category = item.Category.ToLowerInvariant();
var summary = item.Summary.ToLowerInvariant();
var keywords = item.Keywords.Select(keyword => keyword.ToLowerInvariant()).ToArray();
var score = 0;
foreach ( var term in terms )
{
if ( tool.Contains(term, StringComparison.Ordinal) ) score += 8;
if ( category.Contains(term, StringComparison.Ordinal) ) score += 5;
if ( summary.Contains(term, StringComparison.Ordinal) ) score += 2;
if ( keywords.Any(keyword => keyword.Contains(term, StringComparison.Ordinal)) ) score += 4;
}
if ( !string.IsNullOrWhiteSpace(query) && summary.Contains(query, StringComparison.Ordinal) ) score += 3;
return score;
}
private static SboxCapabilityMetadata Read(string tool, string category, string summary, string[] states, string verification, params string[] keywords)
=> new(tool, category, summary, "read", false, states, verification, keywords);
private static SboxCapabilityMetadata Analyze(string tool, string category, string summary, string[] states, string verification, params string[] keywords)
=> new(tool, category, summary, "analyze", false, states, verification, keywords);
private static SboxCapabilityMetadata Write(string tool, string category, string summary, string[] states, string verification, params string[] keywords)
=> new(tool, category, summary, "project-write", true, states, verification, keywords);
private static SboxCapabilityMetadata External(string tool, string category, string summary, string[] states, string verification, params string[] keywords)
=> new(tool, category, summary, "external-write", true, states, verification, keywords);
}
}