@vgai/sdk 0.5.0 → 0.5.2

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.
Files changed (54) hide show
  1. package/package.json +2 -2
  2. package/src/account.ts +5 -4
  3. package/src/cinematic/capabilities-operations.ts +3 -3
  4. package/src/cinematic/cue-operations.ts +11 -11
  5. package/src/cinematic/gsap-operations.ts +5 -5
  6. package/src/cinematic/index.ts +3 -3
  7. package/src/cinematic/preview-operations.ts +10 -10
  8. package/src/cinematic/preview-transport.ts +2 -2
  9. package/src/cinematic/render-operations.ts +15 -15
  10. package/src/cinematic/render-transport.ts +2 -2
  11. package/src/cinematic/theatre-operations.ts +17 -17
  12. package/src/editor/camera-operations.ts +7 -7
  13. package/src/editor/console-operations.ts +5 -5
  14. package/src/editor/hierarchy-operations.ts +5 -5
  15. package/src/editor/index.ts +3 -3
  16. package/src/editor/open-operations.ts +10 -10
  17. package/src/editor/screenshot-operations.ts +5 -5
  18. package/src/editor/selection-operations.ts +7 -7
  19. package/src/editor/session-operations.ts +3 -3
  20. package/src/editor/source-location-operations.ts +6 -6
  21. package/src/editor/transport.ts +24 -11
  22. package/src/errors.ts +8 -8
  23. package/src/generations.ts +12 -1
  24. package/src/http/http-projection.ts +26 -26
  25. package/src/index.ts +22 -30
  26. package/src/mcp/index.ts +2 -2
  27. package/src/mcp/mcp-projection.ts +25 -25
  28. package/src/mcp/mcp-server.ts +2 -2
  29. package/src/operations.ts +5 -5
  30. package/src/play/control-operations.ts +10 -10
  31. package/src/play/debug-command-operations.ts +12 -12
  32. package/src/play/index.ts +3 -3
  33. package/src/play/input-operations.ts +10 -10
  34. package/src/play/lifecycle-operations.ts +11 -11
  35. package/src/play/log-operations.ts +7 -7
  36. package/src/play/run-ticks-operations.ts +8 -8
  37. package/src/play/state-operations.ts +9 -9
  38. package/src/play/status-operations.ts +7 -7
  39. package/src/play/transport.ts +93 -22
  40. package/src/project/asset-operations.ts +8 -8
  41. package/src/project/component-operations.ts +10 -10
  42. package/src/project/discovery-operations.ts +9 -9
  43. package/src/project/entity-operations.ts +8 -8
  44. package/src/project/index.ts +4 -4
  45. package/src/project/input-map-operations.ts +9 -9
  46. package/src/project/inspection-operation.ts +3 -3
  47. package/src/project/manifest-operations.ts +9 -9
  48. package/src/project/scene-operations.ts +14 -14
  49. package/src/project/shared.ts +17 -17
  50. package/src/project-tool-catalog.ts +21 -5
  51. package/src/registry.ts +39 -39
  52. package/src/render/render-cinematic.ts +24 -1
  53. package/src/tools.ts +19 -12
  54. package/src/types.ts +4 -4
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * HTTP projection of the operation registry (B8, §8 B8).
3
3
  *
4
- * The SAME `OperationRegistry` that backs the SDK (`dispatch`) and the CLI
4
+ * The SAME `ToolRegistry` that backs the SDK (`dispatch`) and the CLI
5
5
  * (`packages/vgai-cli/src/oclif/*`, B6) is projected here as a small HTTP
6
6
  * server. Routes are GENERATED from `registry.listOperations()` — one route
7
7
  * per operation — never hand-authored per op: adding an operation to the SDK
@@ -26,14 +26,14 @@
26
26
  * `tools/list` and is built from the very same `listOperations()` summaries.
27
27
  *
28
28
  * OUTCOME SHAPE: every response body is the registry's own
29
- * `OperationOutcome` — `{ ok: true, data }` or `{ ok: false, error }` with a
29
+ * `ToolOutcome` — `{ ok: true, data }` or `{ ok: false, error }` with a
30
30
  * machine-readable `error.code` — byte-for-byte the shape the SDK returns and
31
31
  * the CLI prints under `--json`. The HTTP STATUS is a secondary, best-effort
32
32
  * signal derived from that code (`httpStatusForOutcome`); the authoritative,
33
33
  * cross-projection-identical fact is always `error.code` in the body (§8 B8
34
34
  * AC: "HTTP and MCP return the same structured error codes as SDK and CLI").
35
35
  *
36
- * CONTEXT: an operation's `OperationContext` (projectRoot, editorUrl, an
36
+ * CONTEXT: an operation's `ToolContext` (projectRoot, editorUrl, an
37
37
  * injected editor transport for tests, ...) is produced per request by the
38
38
  * caller-supplied `createContext(req)` (or the static `context`), so the same
39
39
  * server can serve one project or resolve the project per request — the SDK
@@ -45,22 +45,22 @@ import { createServer } from 'node:http';
45
45
  import { z } from 'zod';
46
46
  import { CORE_ERROR_CODES } from '../errors.js';
47
47
  import { operations as defaultRegistry } from '../index.js';
48
- import type { OperationOutcome, OperationRegistry, OperationSummary } from '../registry.js';
49
- import type { OperationContext } from '../types.js';
48
+ import type { ToolOutcome, ToolRegistry, ToolSummary } from '../registry.js';
49
+ import type { ToolContext } from '../types.js';
50
50
 
51
51
  /** Options for building the HTTP projection handler/server. */
52
52
  export interface HttpProjectionOptions {
53
53
  /** Registry to project. Defaults to the shared `operations` registry (same one the CLI uses). */
54
- registry?: OperationRegistry;
54
+ registry?: ToolRegistry;
55
55
  /**
56
56
  * URL prefix every operation route lives under. Default `/op`, so
57
57
  * `project.scene.read` is `POST /op/project.scene.read`. No trailing slash.
58
58
  */
59
59
  basePath?: string;
60
60
  /** Static context handed to every `dispatch`. Ignored when `createContext` is given. */
61
- context?: OperationContext;
61
+ context?: ToolContext;
62
62
  /** Per-request context factory (wins over `context`) — e.g. resolve `projectRoot` from a header. */
63
- createContext?: (req: IncomingMessage) => OperationContext | Promise<OperationContext>;
63
+ createContext?: (req: IncomingMessage) => ToolContext | Promise<ToolContext>;
64
64
  }
65
65
 
66
66
  /** One entry of the `GET <basePath>` manifest — the HTTP analogue of an MCP tool descriptor. */
@@ -73,21 +73,21 @@ export interface HttpRouteDescriptor {
73
73
  mutates: boolean;
74
74
  supportsDryRun: boolean;
75
75
  host: string;
76
- requires: OperationSummary['requires'];
77
- permission: OperationSummary['permission'];
76
+ requires: ToolSummary['requires'];
77
+ permission: ToolSummary['permission'];
78
78
  /** Zod-derived JSON Schema for the request body / `?input=` — same conversion MCP tool schemas use. */
79
79
  inputSchema: unknown;
80
80
  errorCodes: string[];
81
81
  }
82
82
 
83
83
  /** Convert an operation's Zod input schema to JSON Schema — the repo's established `z.toJSONSchema` call (see `scripts/generate-schema.ts`). */
84
- export function inputJsonSchema(summary: OperationSummary): unknown {
84
+ export function inputJsonSchema(summary: ToolSummary): unknown {
85
85
  return z.toJSONSchema(summary.input, { unrepresentable: 'any' });
86
86
  }
87
87
 
88
88
  /** Build the `GET <basePath>` manifest from `listOperations()` — routes are derived, never hand-written. */
89
89
  export function buildRouteManifest(
90
- registry: OperationRegistry,
90
+ registry: ToolRegistry,
91
91
  basePath: string,
92
92
  ): HttpRouteDescriptor[] {
93
93
  return registry
@@ -109,19 +109,19 @@ export function buildRouteManifest(
109
109
  .sort((a, b) => a.name.localeCompare(b.name));
110
110
  }
111
111
 
112
- /** A projection-level (not `dispatch`) failure, still shaped as an `OperationOutcome` so every response body is uniform. */
113
- function failure(code: string, message: string, data?: unknown): OperationOutcome {
112
+ /** A projection-level (not `dispatch`) failure, still shaped as an `ToolOutcome` so every response body is uniform. */
113
+ function failure(code: string, message: string, data?: unknown): ToolOutcome {
114
114
  return { ok: false, error: { code, message, ...(data !== undefined ? { data } : {}) } };
115
115
  }
116
116
 
117
117
  /**
118
- * Map an `OperationOutcome` to an HTTP status. Best-effort ONLY — the
118
+ * Map an `ToolOutcome` to an HTTP status. Best-effort ONLY — the
119
119
  * authoritative, cross-projection-identical signal is `error.code` in the
120
120
  * body, not this status. Success is 200; a handful of well-known core codes
121
121
  * get their conventional status; every other structured failure defaults to
122
122
  * 400 (a caller-actionable problem) rather than 500.
123
123
  */
124
- export function httpStatusForOutcome(outcome: OperationOutcome): number {
124
+ export function httpStatusForOutcome(outcome: ToolOutcome): number {
125
125
  if (outcome.ok) return 200;
126
126
  switch (outcome.error.code) {
127
127
  case CORE_ERROR_CODES.OPERATION_NOT_FOUND:
@@ -205,15 +205,15 @@ async function parseRouteInput(
205
205
  /**
206
206
  * The pure routing core, exported for tests: given a method, pathname and
207
207
  * already-parsed input, resolve the target operation and dispatch it —
208
- * returning the `OperationOutcome` and the HTTP status, with no `http`
208
+ * returning the `ToolOutcome` and the HTTP status, with no `http`
209
209
  * objects involved. `serveHttpRequest` is the thin I/O wrapper around this.
210
210
  */
211
211
  export async function dispatchRoute(
212
- registry: OperationRegistry,
212
+ registry: ToolRegistry,
213
213
  basePath: string,
214
214
  route: ParsedRoute,
215
- ctx: OperationContext,
216
- ): Promise<OperationOutcome> {
215
+ ctx: ToolContext,
216
+ ): Promise<ToolOutcome> {
217
217
  if (route.parseError !== undefined) {
218
218
  // A malformed body is a caller input problem — surface it as the SAME
219
219
  // INVALID_INPUT code the SDK/CLI use for a schema rejection.
@@ -240,18 +240,18 @@ export async function dispatchRoute(
240
240
  return registry.dispatch(route.name, route.input, ctx);
241
241
  }
242
242
 
243
- /** Send an `OperationOutcome` as a JSON response with the derived status. */
244
- function sendOutcome(res: ServerResponse, outcome: OperationOutcome): void {
243
+ /** Send an `ToolOutcome` as a JSON response with the derived status. */
244
+ function sendOutcome(res: ServerResponse, outcome: ToolOutcome): void {
245
245
  const body = `${JSON.stringify(outcome)}\n`;
246
246
  res.writeHead(httpStatusForOutcome(outcome), { 'content-type': 'application/json' });
247
247
  res.end(body);
248
248
  }
249
249
 
250
- /** Resolve the per-request `OperationContext` from the options (static or factory). */
250
+ /** Resolve the per-request `ToolContext` from the options (static or factory). */
251
251
  async function resolveHttpContext(
252
252
  options: HttpProjectionOptions,
253
253
  req: IncomingMessage,
254
- ): Promise<OperationContext> {
254
+ ): Promise<ToolContext> {
255
255
  if (options.createContext) return options.createContext(req);
256
256
  if (options.context) return options.context;
257
257
  return {};
@@ -260,7 +260,7 @@ async function resolveHttpContext(
260
260
  /** The full request lifecycle, factored out of the returned closure to keep each step flat. */
261
261
  async function handleProjectionRequest(
262
262
  options: HttpProjectionOptions,
263
- registry: OperationRegistry,
263
+ registry: ToolRegistry,
264
264
  basePath: string,
265
265
  req: IncomingMessage,
266
266
  res: ServerResponse,
@@ -296,7 +296,7 @@ async function handleProjectionRequest(
296
296
  ? { name, method, input: undefined, parseError: parsed.parseError }
297
297
  : { name, method, input: parsed.input };
298
298
 
299
- let ctx: OperationContext;
299
+ let ctx: ToolContext;
300
300
  try {
301
301
  ctx = await resolveHttpContext(options, req);
302
302
  } catch (err) {
package/src/index.ts CHANGED
@@ -4,9 +4,9 @@ export * from './editor/index.js';
4
4
  export {
5
5
  CORE_ERROR_CODES,
6
6
  type CoreErrorCode,
7
- OperationError,
8
7
  type StructuredIssue,
9
8
  type StructuredOperationError,
9
+ ToolError,
10
10
  toStructuredIssues,
11
11
  } from './errors.js';
12
12
  export * from './http/index.js';
@@ -15,62 +15,54 @@ export {
15
15
  ProjectStatusInput,
16
16
  ProjectStatusResult,
17
17
  projectStatus,
18
- registerBuiltinOperations,
18
+ registerBuiltinTools,
19
19
  } from './operations.js';
20
20
  export * from './perf/index.js';
21
21
  export * from './play/index.js';
22
22
  export * from './project/index.js';
23
23
  export * from './project-tool-catalog.js';
24
24
  export {
25
- defineOperation,
26
- type ErrorDefinition,
27
- type OperationDefinition,
28
- type OperationOutcome,
29
- OperationRegistry,
30
- type OperationSummary,
25
+ defineTool,
26
+ type ToolDefinition,
27
+ type ToolErrorDefinition,
28
+ type ToolOutcome,
29
+ ToolRegistry,
30
+ type ToolSummary,
31
31
  } from './registry.js';
32
32
  export * from './render/index.js';
33
- export type {
34
- ToolContext,
35
- ToolDefinition,
36
- ToolErrorDefinition,
37
- ToolHost,
38
- ToolOutcome,
39
- ToolPermission,
40
- ToolPermissionRisk,
41
- ToolRequirements,
42
- ToolSummary,
43
- } from './tools.js';
44
- export { defineTool, ToolError, ToolRegistry } from './tools.js';
33
+ // The tool vocabulary is exported once, from its defining modules below.
34
+ // `./tools.js` is the consumer-facing entry point and re-exports the same
35
+ // symbols; re-exporting them here too would duplicate every identifier.
36
+ export type { ToolHost, ToolPermission, ToolPermissionRisk, ToolRequirements } from './tools.js';
45
37
  export {
46
38
  type ExecutionHost,
47
39
  type ExecutionRequirements,
48
- OPERATION_NAMESPACES,
49
- type OperationContext,
50
- type OperationNamespace,
51
40
  type PermissionMetadata,
52
41
  type PermissionRisk,
42
+ TOOL_NAMESPACES,
43
+ type ToolContext,
44
+ type ToolNamespace,
53
45
  } from './types.js';
54
46
 
55
47
  import { registerCinematicOperations } from './cinematic/index.js';
56
48
  import { registerEditorOperations } from './editor/index.js';
57
- import { registerBuiltinOperations } from './operations.js';
49
+ import { registerBuiltinTools } from './operations.js';
58
50
  import { registerPlayOperations } from './play/index.js';
59
51
  import { registerProjectOperations } from './project/index.js';
60
- import { OperationRegistry } from './registry.js';
52
+ import { ToolRegistry } from './registry.js';
61
53
 
62
54
  /**
63
55
  * Build a fresh registry preloaded with B1's sample operations plus B2's
64
56
  * real `project.*` operations, B3's real `editor.*` operations, B4's real
65
57
  * `play.*` operations, and B5's real `cinematic.*` operations. Deliberately
66
58
  * separate register calls (not one function grown to cover all five) —
67
- * `registerBuiltinOperations` is B1's own registration function, and its
59
+ * `registerBuiltinTools` is B1's own registration function, and its
68
60
  * registry test asserts exactly what it registers; growing it here would
69
61
  * break that assertion for a reason unrelated to what it actually tests.
70
62
  */
71
- export function createOperationRegistry(): OperationRegistry {
72
- const registry = new OperationRegistry();
73
- registerBuiltinOperations(registry);
63
+ export function createToolRegistry(): ToolRegistry {
64
+ const registry = new ToolRegistry();
65
+ registerBuiltinTools(registry);
74
66
  registerProjectOperations(registry);
75
67
  registerEditorOperations(registry);
76
68
  registerPlayOperations(registry);
@@ -79,4 +71,4 @@ export function createOperationRegistry(): OperationRegistry {
79
71
  }
80
72
 
81
73
  /** Convenience default registry — the same one CLI/HTTP/MCP projections (B6-B8) will build on. */
82
- export const operations = createOperationRegistry();
74
+ export const operations = createToolRegistry();
package/src/mcp/index.ts CHANGED
@@ -11,8 +11,8 @@ export {
11
11
  type McpProjectionOptions,
12
12
  type McpResource,
13
13
  type McpTool,
14
- operationToResource,
15
- operationToTool,
14
+ toolToMcpResource,
15
+ toolToMcpTool,
16
16
  outcomeToToolResult,
17
17
  toolInputSchema,
18
18
  } from './mcp-projection.js';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * MCP projection of the operation registry (B8, §8 B8).
3
3
  *
4
- * The SAME `OperationRegistry` behind the SDK, the CLI (B6), and the HTTP
4
+ * The SAME `ToolRegistry` behind the SDK, the CLI (B6), and the HTTP
5
5
  * projection is registered here as MCP TOOLS (and, for a principled subset,
6
6
  * MCP RESOURCES). Every tool is derived from a `listOperations()` summary —
7
7
  * name, description, and a Zod-derived JSON Schema for its input — so adding
@@ -40,8 +40,8 @@
40
40
  import { z } from 'zod';
41
41
  import { CORE_ERROR_CODES } from '../errors.js';
42
42
  import { operations as defaultRegistry } from '../index.js';
43
- import type { OperationOutcome, OperationRegistry, OperationSummary } from '../registry.js';
44
- import type { OperationContext } from '../types.js';
43
+ import type { ToolOutcome, ToolRegistry, ToolSummary } from '../registry.js';
44
+ import type { ToolContext } from '../types.js';
45
45
 
46
46
  /** URI scheme/prefix under which read-only-state operations are also exposed as MCP resources. */
47
47
  export const MCP_RESOURCE_PREFIX = 'vgai://op/';
@@ -72,11 +72,11 @@ export interface McpResource {
72
72
 
73
73
  export interface McpProjectionOptions {
74
74
  /** Registry to project. Defaults to the shared `operations` registry (same one the CLI/HTTP projections use). */
75
- registry?: OperationRegistry;
75
+ registry?: ToolRegistry;
76
76
  /** Static context handed to every dispatch. Ignored when `createContext` is given. */
77
- context?: OperationContext;
77
+ context?: ToolContext;
78
78
  /** Per-call context factory (wins over `context`). */
79
- createContext?: () => OperationContext | Promise<OperationContext>;
79
+ createContext?: () => ToolContext | Promise<ToolContext>;
80
80
  }
81
81
 
82
82
  /** The Zod-derived JSON Schema for an op's input — the SAME conversion the HTTP manifest uses. */
@@ -91,7 +91,7 @@ function objectWireSchema(schema: Record<string, unknown>): Record<string, unkno
91
91
  }
92
92
 
93
93
  function fullOperationSchema(
94
- summary: OperationSummary,
94
+ summary: ToolSummary,
95
95
  direction: 'input' | 'output',
96
96
  ): Record<string, unknown> {
97
97
  const operationSchema = objectWireSchema(
@@ -122,7 +122,7 @@ function fullOperationSchema(
122
122
  };
123
123
  }
124
124
 
125
- function schemaResourceUri(summary: OperationSummary, direction: 'input' | 'output'): string {
125
+ function schemaResourceUri(summary: ToolSummary, direction: 'input' | 'output'): string {
126
126
  return `${MCP_SCHEMA_RESOURCE_PREFIX}${direction}/${summary.name}`;
127
127
  }
128
128
 
@@ -143,7 +143,7 @@ function compactLargeProperty(value: unknown, resourceUri: string, limit = 4_096
143
143
  }
144
144
 
145
145
  function compactOperationSchema(
146
- summary: OperationSummary,
146
+ summary: ToolSummary,
147
147
  direction: 'input' | 'output',
148
148
  ): Record<string, unknown> {
149
149
  const full = fullOperationSchema(summary, direction);
@@ -176,12 +176,12 @@ function compactOperationSchema(
176
176
  }
177
177
 
178
178
  /** Compact MCP-list schema; the full version is exposed as an on-demand resource when large. */
179
- export function toolInputSchema(summary: OperationSummary): unknown {
179
+ export function toolInputSchema(summary: ToolSummary): unknown {
180
180
  return compactOperationSchema(summary, 'input');
181
181
  }
182
182
 
183
183
  /** Derive one MCP tool from an operation summary: dotted name (mirrors §5.7 hierarchy), description, Zod-derived schema. */
184
- export function operationToTool(summary: OperationSummary): McpTool {
184
+ export function toolToMcpTool(summary: ToolSummary): McpTool {
185
185
  return {
186
186
  name: summary.name,
187
187
  description: `${summary.summary}\n\n${summary.description}`.trim(),
@@ -199,13 +199,13 @@ export function operationToTool(summary: OperationSummary): McpTool {
199
199
  }
200
200
 
201
201
  /** True when a read-only op takes no required input, so it names readable STATE addressable by URI alone (resource-worthy). See module jsdoc. */
202
- export function isResourceEligible(summary: OperationSummary): boolean {
202
+ export function isResourceEligible(summary: ToolSummary): boolean {
203
203
  if (summary.mutates) return false;
204
204
  return summary.input.safeParse({}).success;
205
205
  }
206
206
 
207
207
  /** Derive the MCP resource descriptor for a resource-eligible op. */
208
- export function operationToResource(summary: OperationSummary): McpResource {
208
+ export function toolToMcpResource(summary: ToolSummary): McpResource {
209
209
  return {
210
210
  uri: `${MCP_RESOURCE_PREFIX}${summary.name}`,
211
211
  name: summary.name,
@@ -214,7 +214,7 @@ export function operationToResource(summary: OperationSummary): McpResource {
214
214
  };
215
215
  }
216
216
 
217
- function schemaResources(summary: OperationSummary): McpResource[] {
217
+ function schemaResources(summary: ToolSummary): McpResource[] {
218
218
  const resources: McpResource[] = [];
219
219
  for (const direction of ['input', 'output'] as const) {
220
220
  const full = fullOperationSchema(summary, direction);
@@ -267,7 +267,7 @@ function rpcError(
267
267
  }
268
268
 
269
269
  /**
270
- * Wrap an `OperationOutcome` as an MCP tool-call result. MCP tool results are
270
+ * Wrap an `ToolOutcome` as an MCP tool-call result. MCP tool results are
271
271
  * `{ content: [...], isError?, structuredContent? }`; we put the outcome JSON
272
272
  * as a `text` content block (so a model sees it) AND — crucially for machine
273
273
  * consumers and the contract test — pass it through verbatim in
@@ -275,10 +275,10 @@ function rpcError(
275
275
  * tool result) so `error.code` survives unchanged. `isError` mirrors
276
276
  * `!outcome.ok`.
277
277
  */
278
- export function outcomeToToolResult(outcome: OperationOutcome): {
278
+ export function outcomeToToolResult(outcome: ToolOutcome): {
279
279
  content: { type: 'text'; text: string }[];
280
280
  isError: boolean;
281
- structuredContent: OperationOutcome;
281
+ structuredContent: ToolOutcome;
282
282
  } {
283
283
  return {
284
284
  content: [{ type: 'text', text: JSON.stringify(outcome) }],
@@ -293,18 +293,18 @@ export function outcomeToToolResult(outcome: OperationOutcome): {
293
293
  * transport (or a test) drives.
294
294
  */
295
295
  export interface McpProjection {
296
- registry: OperationRegistry;
296
+ registry: ToolRegistry;
297
297
  listTools(): McpTool[];
298
298
  listResources(): McpResource[];
299
- callTool(name: string, args: unknown): Promise<OperationOutcome>;
300
- readResource(uri: string): Promise<OperationOutcome>;
299
+ callTool(name: string, args: unknown): Promise<ToolOutcome>;
300
+ readResource(uri: string): Promise<ToolOutcome>;
301
301
  handle(request: JsonRpcRequest): Promise<JsonRpcResponse>;
302
302
  }
303
303
 
304
304
  export function createMcpProjection(options: McpProjectionOptions = {}): McpProjection {
305
305
  const registry = options.registry ?? defaultRegistry;
306
306
 
307
- async function resolveContext(): Promise<OperationContext> {
307
+ async function resolveContext(): Promise<ToolContext> {
308
308
  if (options.createContext) return options.createContext();
309
309
  return options.context ?? {};
310
310
  }
@@ -312,25 +312,25 @@ export function createMcpProjection(options: McpProjectionOptions = {}): McpProj
312
312
  function listTools(): McpTool[] {
313
313
  return registry
314
314
  .listOperations()
315
- .map(operationToTool)
315
+ .map(toolToMcpTool)
316
316
  .sort((a, b) => a.name.localeCompare(b.name));
317
317
  }
318
318
 
319
319
  function listResources(): McpResource[] {
320
320
  const summaries = registry.listOperations();
321
321
  return [
322
- ...summaries.filter(isResourceEligible).map(operationToResource),
322
+ ...summaries.filter(isResourceEligible).map(toolToMcpResource),
323
323
  ...summaries.flatMap(schemaResources),
324
324
  ].sort((a, b) => a.uri.localeCompare(b.uri));
325
325
  }
326
326
 
327
- async function callTool(name: string, args: unknown): Promise<OperationOutcome> {
327
+ async function callTool(name: string, args: unknown): Promise<ToolOutcome> {
328
328
  // dispatch already returns OPERATION_NOT_FOUND for an unknown name and
329
329
  // INVALID_INPUT for a bad-shaped `args` — same codes as SDK/CLI/HTTP.
330
330
  return registry.dispatch(name, args ?? {}, await resolveContext());
331
331
  }
332
332
 
333
- async function readResource(uri: string): Promise<OperationOutcome> {
333
+ async function readResource(uri: string): Promise<ToolOutcome> {
334
334
  if (uri.startsWith(MCP_SCHEMA_RESOURCE_PREFIX)) {
335
335
  const match = /^vgai:\/\/schema\/(input|output)\/(.+)$/.exec(uri);
336
336
  const summary = match
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * This deliberately uses the official SDK's lower-level `Server` rather than
5
5
  * re-registering operations through `McpServer.registerTool`. VGAI already has
6
- * one authoritative validation/dispatch layer (`OperationRegistry`): letting
6
+ * one authoritative validation/dispatch layer (`ToolRegistry`): letting
7
7
  * the high-level helper pre-validate arguments would turn malformed operation
8
8
  * input into an SDK protocol error before the registry could return the same
9
9
  * structured `INVALID_INPUT` outcome as SDK/CLI/HTTP. The lower-level server
@@ -48,7 +48,7 @@ export const DEFAULT_MCP_INSTRUCTIONS =
48
48
  'success. For an existing non-VGAI game, inspect first and never rewrite source without consent. ' +
49
49
  'Read operations are safe to call for discovery. Before calling a write or destructive tool, ' +
50
50
  'inspect its annotations and description, preserve project source, and use dryRun when the ' +
51
- 'tool offers it. Tool results are VGAI OperationOutcome objects: {ok:true,data} on success or ' +
51
+ 'tool offers it. Tool results are VGAI ToolOutcome objects: {ok:true,data} on success or ' +
52
52
  '{ok:false,error:{code,message,...}} on failure. Operations requiring an editor target the ' +
53
53
  'live vgai edit session matching the project unless VGAI_EDITOR_URL/--url selected one explicitly.';
54
54
 
package/src/operations.ts CHANGED
@@ -21,8 +21,8 @@
21
21
  import { existsSync } from 'node:fs';
22
22
  import { join } from 'node:path';
23
23
  import { z } from 'zod';
24
- import { OperationError } from './errors.js';
25
- import { defineOperation, type OperationRegistry } from './registry.js';
24
+ import { ToolError } from './errors.js';
25
+ import { defineTool, type ToolRegistry } from './registry.js';
26
26
 
27
27
  // ---------------------------------------------------------------------------
28
28
  // project.status — real, file-native (B2 project.* stand-in)
@@ -41,7 +41,7 @@ export const ProjectStatusResult = z
41
41
  })
42
42
  .describe('Discovery result for the project at ctx.projectRoot.');
43
43
 
44
- export const projectStatus = defineOperation({
44
+ export const projectStatus = defineTool({
45
45
  name: 'project.status',
46
46
  summary: 'Report whether a vgai project manifest exists at the execution context project root.',
47
47
  description:
@@ -64,7 +64,7 @@ export const projectStatus = defineOperation({
64
64
  permission: { risk: 'read', summary: "Reads one file's existence under projectRoot; no writes." },
65
65
  async impl(_input, ctx) {
66
66
  if (!ctx.projectRoot) {
67
- throw new OperationError(
67
+ throw new ToolError(
68
68
  'NO_PROJECT_ROOT',
69
69
  'dispatch() was called without ctx.projectRoot.',
70
70
  {},
@@ -78,6 +78,6 @@ export const projectStatus = defineOperation({
78
78
  });
79
79
 
80
80
  /** Register every B1 sample operation onto `registry`. */
81
- export function registerBuiltinOperations(registry: OperationRegistry): void {
81
+ export function registerBuiltinTools(registry: ToolRegistry): void {
82
82
  registry.register(projectStatus);
83
83
  }
@@ -34,8 +34,8 @@
34
34
  */
35
35
 
36
36
  import { z } from 'zod';
37
- import { OperationError } from '../errors.js';
38
- import { defineOperation, type OperationRegistry } from '../registry.js';
37
+ import { ToolError } from '../errors.js';
38
+ import { defineTool, type ToolRegistry } from '../registry.js';
39
39
  import {
40
40
  getPlayTransport,
41
41
  PLAY_COMMAND_TIMEOUT_MS,
@@ -88,7 +88,7 @@ const PlaySeedSetInput = z.object({
88
88
  seed: z.number().int().describe('Deterministic seed value to apply to the running play session.'),
89
89
  });
90
90
 
91
- export const playSeedSet = defineOperation({
91
+ export const playSeedSet = defineTool({
92
92
  name: 'play.seed.set',
93
93
  summary: 'Live-reseed a running play session (future draws only).',
94
94
  description:
@@ -122,7 +122,7 @@ export const playSeedSet = defineOperation({
122
122
  'play.seed.set',
123
123
  ).catch(() => undefined);
124
124
  if (!result) {
125
- throw new OperationError(
125
+ throw new ToolError(
126
126
  'SEED_CONTROL_UNSUPPORTED',
127
127
  'The connected editor page predates the set-seed relay command — reload it.',
128
128
  {},
@@ -132,9 +132,9 @@ export const playSeedSet = defineOperation({
132
132
  const marker = relayErrorMarker(result);
133
133
  const message = result.error ?? 'set-seed command failed';
134
134
  if (marker?.code === 'DETERMINISM_NOT_DECLARED') {
135
- throw new OperationError('DETERMINISM_NOT_DECLARED', message, {});
135
+ throw new ToolError('DETERMINISM_NOT_DECLARED', message, {});
136
136
  }
137
- throw new OperationError('COMMAND_FAILED', message, { message });
137
+ throw new ToolError('COMMAND_FAILED', message, { message });
138
138
  }
139
139
  return { ok: true as const };
140
140
  },
@@ -152,7 +152,7 @@ const PlayTimeScaleSetInput = z.object({
152
152
  .describe('Simulation time-scale to apply (clamped [0,8] to match GameLoop.timeScale).'),
153
153
  });
154
154
 
155
- export const playTimeScaleSet = defineOperation({
155
+ export const playTimeScaleSet = defineTool({
156
156
  name: 'play.timeScale.set',
157
157
  summary: "Set a running play session's simulation time-scale.",
158
158
  description:
@@ -183,7 +183,7 @@ export const playTimeScaleSet = defineOperation({
183
183
  'play.timeScale.set',
184
184
  ).catch(() => undefined);
185
185
  if (!result) {
186
- throw new OperationError(
186
+ throw new ToolError(
187
187
  'TIME_SCALE_CONTROL_UNSUPPORTED',
188
188
  "The connected editor has no wire-level command to change a live play session's " +
189
189
  'time-scale today.',
@@ -191,7 +191,7 @@ export const playTimeScaleSet = defineOperation({
191
191
  );
192
192
  }
193
193
  if (!result.ok) {
194
- throw new OperationError('COMMAND_FAILED', result.error ?? 'set-time-scale command failed', {
194
+ throw new ToolError('COMMAND_FAILED', result.error ?? 'set-time-scale command failed', {
195
195
  message: result.error ?? 'set-time-scale command failed',
196
196
  });
197
197
  }
@@ -199,7 +199,7 @@ export const playTimeScaleSet = defineOperation({
199
199
  },
200
200
  });
201
201
 
202
- export function registerControlOperations(registry: OperationRegistry): void {
202
+ export function registerControlOperations(registry: ToolRegistry): void {
203
203
  registry.register(playSeedSet);
204
204
  registry.register(playTimeScaleSet);
205
205
  }
@@ -22,8 +22,8 @@
22
22
  */
23
23
 
24
24
  import { z } from 'zod';
25
- import { OperationError } from '../errors.js';
26
- import { defineOperation, type OperationRegistry } from '../registry.js';
25
+ import { ToolError } from '../errors.js';
26
+ import { defineTool, type ToolRegistry } from '../registry.js';
27
27
  import {
28
28
  getPlayTransport,
29
29
  PLAY_COMMAND_TIMEOUT_MS,
@@ -99,7 +99,7 @@ const PlayDebugCommandListResult = z.object({
99
99
  .describe('Every debug command the running game registered, with locus and arg shapes.'),
100
100
  });
101
101
 
102
- export const playDebugCommandList = defineOperation({
102
+ export const playDebugCommandList = defineTool({
103
103
  name: 'play.debugCommand.list',
104
104
  summary: 'List the debug commands a running play session registers, with locus and arg shapes.',
105
105
  description:
@@ -131,7 +131,7 @@ export const playDebugCommandList = defineOperation({
131
131
  'play.debugCommand.list',
132
132
  ).catch(() => undefined);
133
133
  if (result === undefined) {
134
- throw new OperationError(
134
+ throw new ToolError(
135
135
  'DEBUG_COMMANDS_UNSUPPORTED',
136
136
  'The connected editor page predates the debug-command relay commands — reload it.',
137
137
  {},
@@ -139,7 +139,7 @@ export const playDebugCommandList = defineOperation({
139
139
  }
140
140
  if (!result.ok) {
141
141
  const message = result.error ?? 'list-debug-commands command failed';
142
- throw new OperationError('COMMAND_FAILED', message, { message });
142
+ throw new ToolError('COMMAND_FAILED', message, { message });
143
143
  }
144
144
  const commands =
145
145
  (
@@ -174,7 +174,7 @@ const PlayDebugCommandInvokeResult = z.object({
174
174
  result: z.unknown().describe("The command's return value (JSON-serializable), when any."),
175
175
  });
176
176
 
177
- export const playDebugCommandInvoke = defineOperation({
177
+ export const playDebugCommandInvoke = defineTool({
178
178
  name: 'play.debugCommand.invoke',
179
179
  summary: 'Invoke a game-registered debug command in a running play session.',
180
180
  description:
@@ -211,7 +211,7 @@ export const playDebugCommandInvoke = defineOperation({
211
211
  'play.debugCommand.invoke',
212
212
  ).catch(() => undefined);
213
213
  if (result === undefined) {
214
- throw new OperationError(
214
+ throw new ToolError(
215
215
  'DEBUG_COMMANDS_UNSUPPORTED',
216
216
  'The connected editor page predates the debug-command relay commands — reload it.',
217
217
  {},
@@ -222,24 +222,24 @@ export const playDebugCommandInvoke = defineOperation({
222
222
  const message = result.error ?? 'invoke-debug-command command failed';
223
223
  switch (marker?.code) {
224
224
  case 'DEBUG_COMMAND_NOT_REGISTERED':
225
- throw new OperationError('DEBUG_COMMAND_NOT_REGISTERED', message, {
225
+ throw new ToolError('DEBUG_COMMAND_NOT_REGISTERED', message, {
226
226
  registered: relayStringArray(marker.data['registered']),
227
227
  });
228
228
  case 'DEBUG_COMMAND_ARGS_INVALID':
229
- throw new OperationError('DEBUG_COMMAND_ARGS_INVALID', message, {
229
+ throw new ToolError('DEBUG_COMMAND_ARGS_INVALID', message, {
230
230
  issues: Array.isArray(marker.data['issues']) ? marker.data['issues'] : [],
231
231
  });
232
232
  case 'DEBUG_COMMAND_FAILED':
233
- throw new OperationError('DEBUG_COMMAND_FAILED', message, marker.data);
233
+ throw new ToolError('DEBUG_COMMAND_FAILED', message, marker.data);
234
234
  default:
235
- throw new OperationError('COMMAND_FAILED', message, { message });
235
+ throw new ToolError('COMMAND_FAILED', message, { message });
236
236
  }
237
237
  }
238
238
  return { result: (result.data as { result?: unknown } | undefined)?.result };
239
239
  },
240
240
  });
241
241
 
242
- export function registerDebugCommandOperations(registry: OperationRegistry): void {
242
+ export function registerDebugCommandOperations(registry: ToolRegistry): void {
243
243
  registry.register(playDebugCommandList);
244
244
  registry.register(playDebugCommandInvoke);
245
245
  }