@frontmcp/plugins 0.4.1 → 0.5.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.
Files changed (98) hide show
  1. package/package.json +8 -3
  2. package/src/cache/cache.plugin.js +27 -25
  3. package/src/cache/cache.plugin.js.map +1 -1
  4. package/src/cache/providers/cache-memory.provider.js +2 -1
  5. package/src/cache/providers/cache-memory.provider.js.map +1 -1
  6. package/src/cache/providers/cache-redis.provider.js +1 -0
  7. package/src/cache/providers/cache-redis.provider.js.map +1 -1
  8. package/src/codecall/README.md +999 -0
  9. package/src/codecall/codecall.plugin.d.ts +41 -0
  10. package/src/codecall/codecall.plugin.js +152 -0
  11. package/src/codecall/codecall.plugin.js.map +1 -0
  12. package/src/codecall/codecall.symbol.d.ts +106 -0
  13. package/src/codecall/codecall.symbol.js +4 -0
  14. package/src/codecall/codecall.symbol.js.map +1 -0
  15. package/src/codecall/codecall.types.d.ts +289 -0
  16. package/src/codecall/codecall.types.js +258 -0
  17. package/src/codecall/codecall.types.js.map +1 -0
  18. package/src/codecall/errors/index.d.ts +1 -0
  19. package/src/codecall/errors/index.js +6 -0
  20. package/src/codecall/errors/index.js.map +1 -0
  21. package/src/codecall/errors/tool-call.errors.d.ts +79 -0
  22. package/src/codecall/errors/tool-call.errors.js +119 -0
  23. package/src/codecall/errors/tool-call.errors.js.map +1 -0
  24. package/src/codecall/index.d.ts +2 -0
  25. package/src/codecall/index.js +8 -0
  26. package/src/codecall/index.js.map +1 -0
  27. package/src/codecall/providers/code-call.config.d.ts +29 -0
  28. package/src/codecall/providers/code-call.config.js +120 -0
  29. package/src/codecall/providers/code-call.config.js.map +1 -0
  30. package/src/codecall/security/index.d.ts +2 -0
  31. package/src/codecall/security/index.js +7 -0
  32. package/src/codecall/security/index.js.map +1 -0
  33. package/src/codecall/security/self-reference-guard.d.ts +32 -0
  34. package/src/codecall/security/self-reference-guard.js +70 -0
  35. package/src/codecall/security/self-reference-guard.js.map +1 -0
  36. package/src/codecall/security/tool-access-control.service.d.ts +104 -0
  37. package/src/codecall/security/tool-access-control.service.js +170 -0
  38. package/src/codecall/security/tool-access-control.service.js.map +1 -0
  39. package/src/codecall/services/audit-logger.service.d.ts +186 -0
  40. package/src/codecall/services/audit-logger.service.js +322 -0
  41. package/src/codecall/services/audit-logger.service.js.map +1 -0
  42. package/src/codecall/services/enclave.service.d.ts +62 -0
  43. package/src/codecall/services/enclave.service.js +214 -0
  44. package/src/codecall/services/enclave.service.js.map +1 -0
  45. package/src/codecall/services/error-enrichment.service.d.ts +94 -0
  46. package/src/codecall/services/error-enrichment.service.js +387 -0
  47. package/src/codecall/services/error-enrichment.service.js.map +1 -0
  48. package/src/codecall/services/index.d.ts +6 -0
  49. package/src/codecall/services/index.js +13 -0
  50. package/src/codecall/services/index.js.map +1 -0
  51. package/src/codecall/services/output-sanitizer.d.ts +86 -0
  52. package/src/codecall/services/output-sanitizer.js +260 -0
  53. package/src/codecall/services/output-sanitizer.js.map +1 -0
  54. package/src/codecall/services/synonym-expansion.service.d.ts +66 -0
  55. package/src/codecall/services/synonym-expansion.service.js +374 -0
  56. package/src/codecall/services/synonym-expansion.service.js.map +1 -0
  57. package/src/codecall/services/tool-search.service.d.ts +175 -0
  58. package/src/codecall/services/tool-search.service.js +587 -0
  59. package/src/codecall/services/tool-search.service.js.map +1 -0
  60. package/src/codecall/tools/describe.schema.d.ts +28 -0
  61. package/src/codecall/tools/describe.schema.js +67 -0
  62. package/src/codecall/tools/describe.schema.js.map +1 -0
  63. package/src/codecall/tools/describe.tool.d.ts +35 -0
  64. package/src/codecall/tools/describe.tool.js +207 -0
  65. package/src/codecall/tools/describe.tool.js.map +1 -0
  66. package/src/codecall/tools/execute.schema.d.ts +115 -0
  67. package/src/codecall/tools/execute.schema.js +116 -0
  68. package/src/codecall/tools/execute.schema.js.map +1 -0
  69. package/src/codecall/tools/execute.tool.d.ts +5 -0
  70. package/src/codecall/tools/execute.tool.js +238 -0
  71. package/src/codecall/tools/execute.tool.js.map +1 -0
  72. package/src/codecall/tools/index.d.ts +4 -0
  73. package/src/codecall/tools/index.js +13 -0
  74. package/src/codecall/tools/index.js.map +1 -0
  75. package/src/codecall/tools/invoke.schema.d.ts +55 -0
  76. package/src/codecall/tools/invoke.schema.js +27 -0
  77. package/src/codecall/tools/invoke.schema.js.map +1 -0
  78. package/src/codecall/tools/invoke.tool.d.ts +13 -0
  79. package/src/codecall/tools/invoke.tool.js +70 -0
  80. package/src/codecall/tools/invoke.tool.js.map +1 -0
  81. package/src/codecall/tools/search.schema.d.ts +30 -0
  82. package/src/codecall/tools/search.schema.js +60 -0
  83. package/src/codecall/tools/search.schema.js.map +1 -0
  84. package/src/codecall/tools/search.tool.d.ts +5 -0
  85. package/src/codecall/tools/search.tool.js +108 -0
  86. package/src/codecall/tools/search.tool.js.map +1 -0
  87. package/src/codecall/utils/describe.utils.d.ts +86 -0
  88. package/src/codecall/utils/describe.utils.js +531 -0
  89. package/src/codecall/utils/describe.utils.js.map +1 -0
  90. package/src/codecall/utils/index.d.ts +2 -0
  91. package/src/codecall/utils/index.js +7 -0
  92. package/src/codecall/utils/index.js.map +1 -0
  93. package/src/codecall/utils/mcp-result.d.ts +6 -0
  94. package/src/codecall/utils/mcp-result.js +36 -0
  95. package/src/codecall/utils/mcp-result.js.map +1 -0
  96. package/src/index.d.ts +2 -0
  97. package/src/index.js +3 -1
  98. package/src/index.js.map +1 -1
@@ -0,0 +1,999 @@
1
+ # CodeCall Plugin (internal docs)
2
+
3
+ This document is for **contributors** working on the CodeCall plugin implementation.
4
+
5
+ For user-facing docs, see the **CodeCall** page in the main FrontMCP documentation (Mintlify).
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ **CodeCall** replaces direct MCP tool calls with **code calls**:
12
+
13
+ - The LLM no longer calls each tool directly.
14
+ - Instead, it interacts with a small meta-API:
15
+ - `codecall.search`
16
+ - `codecall.describe`
17
+ - `codecall.execute`
18
+ - (optional) `codecall.invoke` for direct calls without a VM
19
+ - The LLM writes **JavaScript execution plans** that run inside a `vm2` sandbox and call real tools through well-defined globals.
20
+
21
+ Goals:
22
+
23
+ - Support **dozens or hundreds of tools** (inline + adapters like OpenAPI) without flooding `list_tools`.
24
+ - Let the LLM perform:
25
+ - Multi-step workflows.
26
+ - Cross-tool joins and aggregations.
27
+ - Post-processing and custom filtering.
28
+ - Enforce safety with:
29
+ - AST validation.
30
+ - CSP-style VM presets.
31
+ - Strict sandboxing (no FS / network / Node builtins).
32
+ - Work across **multiple apps** in the same FrontMCP server, with filtering by `appId`.
33
+ - Preserve **PII/privacy guarantees** by always going through the normal tool pipeline and plugins.
34
+
35
+ ---
36
+
37
+ ## High-level architecture
38
+
39
+ 1. **Tool indexing**
40
+
41
+ - On app/bootstrap, the plugin receives the resolved list of tools (inline + adapters) across all apps.
42
+ - It builds an index containing:
43
+ - `name`
44
+ - `description`
45
+ - `source` (e.g. `inline`, `openapi`)
46
+ - `appId`
47
+ - input schema (`inputSchema`)
48
+ - optional output schema (`outputSchema`)
49
+ - An optional `includeTools` predicate can drop tools from the index entirely.
50
+
51
+ 2. **Modes + metadata**
52
+
53
+ - CodeCall has a `mode` that controls:
54
+ - Which tools appear in `list_tools`.
55
+ - Which tools are indexed and callable by CodeCall.
56
+ - Tools are annotated with `codecall` metadata via the `@Tool` decorator.
57
+
58
+ 3. **Multi-app / filter-aware search**
59
+
60
+ - `codecall.search` accepts a `filter` object that can include:
61
+ - `appIds` – limit search to one or more apps (e.g. `['user', 'billing']`).
62
+ - Other tags or flags.
63
+ - The LLM can:
64
+ - Search in a single app (e.g. only `user` tools).
65
+ - Search in multiple apps (e.g. join `user` + `billing`).
66
+ - Or omit `appIds` to search across all apps.
67
+
68
+ 4. **`list_tools` interception**
69
+
70
+ - CodeCall intercepts tool listing and replaces it with:
71
+ - `codecall.search`
72
+ - `codecall.describe`
73
+ - `codecall.execute`
74
+ - (optionally) `codecall.invoke`
75
+ - plus any tools whose metadata requires them to stay visible.
76
+
77
+ 5. **Search / describe**
78
+
79
+ - `codecall.search`:
80
+ - Scores tools against a natural-language query (optionally scoped by `filter`).
81
+ - Returns the top `topK`.
82
+ - `codecall.describe`:
83
+ - Returns JSON-schema-like shapes for selected tools so the LLM can generate valid JS code.
84
+
85
+ 6. **Plan execution**
86
+
87
+ - `codecall.execute`:
88
+ - Parses the script into an AST.
89
+ - Validates the AST against security rules (no banned builtins/globals, optional loop ban).
90
+ - Applies a CSP-style VM preset + overrides.
91
+ - Spawns a `vm2` sandbox with:
92
+ - `callTool`
93
+ - `getTool`
94
+ - `codecallContext`
95
+ - optional `console`
96
+ - optional `mcpLog` / `mcpNotify`
97
+ - Executes with a timeout.
98
+ - Sends MCP notifications for **each tool call start/end**.
99
+ - Returns a normalized result with a `status` discriminant.
100
+
101
+ 7. **Direct calls (no VM)**
102
+
103
+ - If enabled, CodeCall exposes `codecall.invoke`:
104
+ - Calls tools directly via the underlying tool pipeline (no VM, no JS plan).
105
+ - Still uses the same plugin chain, PII behavior, and error model.
106
+
107
+ 8. **Sessions & tool list notifications**
108
+ - If the transport supports **tool list change notifications** and **transport session IDs**:
109
+ - Clients can cache tool descriptions per session.
110
+ - CodeCall can emit “tool list changed” events when relevant tools change.
111
+ - If not supported:
112
+ - Orchestrators should re-run `codecall.describe` before `codecall.execute` when they rely on fresh schemas.
113
+
114
+ ---
115
+
116
+ ## PII, privacy, and plugin chaining
117
+
118
+ CodeCall **does not bypass** the normal FrontMCP tool lifecycle.
119
+
120
+ Internally, `callTool(name, input)` inside the VM must delegate to the same
121
+ **tool call pipeline** used by direct MCP tool calls. That means:
122
+
123
+ - Any existing plugins that hook into **tool call lifecycle** (e.g. PII scrubbing,
124
+ masking, audit logging, rate limiting, auth) will still run as usual.
125
+ - CodeCall does not introduce extra data paths:
126
+ - The data seen by the VM is exactly what a normal tool call would see
127
+ _after_ all relevant plugins have run.
128
+ - The result that CodeCall returns is equivalent to calling the same tools
129
+ one-by-one outside of CodeCall.
130
+
131
+ For PII specifically:
132
+
133
+ - A PII plugin can hook:
134
+ - **Before** tool execution to scrub/mask sensitive fields in the input.
135
+ - **After** tool execution to scrub/mask sensitive data in the output.
136
+ - CodeCall’s `callTool` simply calls into the underlying tool call engine.
137
+ - Therefore:
138
+ - CodeCall cannot “leak” data that the PII plugin wouldn’t already allow.
139
+ - Any PII redaction/obfuscation logic stays fully in control of the PII plugin.
140
+
141
+ In other words: CodeCall **reuses** the same tool pipeline; it does not create a new backdoor.
142
+
143
+ ---
144
+
145
+ ## Tool metadata extension
146
+
147
+ CodeCall extends `@Tool` options by adding a `codecall` section via `declare global`.
148
+
149
+ Conceptual shape:
150
+
151
+ ```ts
152
+ declare global {
153
+ interface ToolOptions {
154
+ codecall?: {
155
+ /**
156
+ * If true, this tool stays visible in list_tools alongside the
157
+ * CodeCall meta-tools, depending on mode.
158
+ */
159
+ visibleInListTools?: boolean;
160
+
161
+ /**
162
+ * Enable/disable this tool for CodeCall search/execute.
163
+ *
164
+ * Defaults depend on CodeCall mode:
165
+ * - 'codecall_only': default true
166
+ * - 'codecall_opt_in': default false
167
+ * - 'metadata_driven': default false (must be explicit)
168
+ */
169
+ enabledInCodeCall?: boolean;
170
+ };
171
+ }
172
+ }
173
+ ```
174
+
175
+ Example tool:
176
+
177
+ ```ts
178
+ @Tool({
179
+ name: 'users:list',
180
+ description: 'List users with pagination',
181
+ codecall: {
182
+ enabledInCodeCall: true,
183
+ visibleInListTools: false,
184
+ },
185
+ })
186
+ export class ListUsersTool {
187
+ // ...
188
+ }
189
+ ```
190
+
191
+ Implementation detail: ship a `.d.ts` or `types` entry that adds this `codecall` field globally when the package is imported.
192
+
193
+ ---
194
+
195
+ ## Modes
196
+
197
+ ### Type
198
+
199
+ ```ts
200
+ export type CodeCallMode = 'codecall_only' | 'codecall_opt_in' | 'metadata_driven';
201
+ ```
202
+
203
+ ### Semantics & best practices
204
+
205
+ #### `codecall_only` (recommended default)
206
+
207
+ **Use when:**
208
+
209
+ - There are **many tools** (dozens/hundreds).
210
+ - You want them **all available via CodeCall**, but **not all listed** in `list_tools`.
211
+
212
+ **Behavior:**
213
+
214
+ - `list_tools`:
215
+
216
+ - Hide all tools by default.
217
+ - Show tools with `codecall.visibleInListTools === true`.
218
+
219
+ - CodeCall index:
220
+
221
+ - Include all tools by default.
222
+ - Exclude tools if:
223
+
224
+ - `includeTools(tool) === false`, or
225
+ - `codecall.enabledInCodeCall === false`.
226
+
227
+ #### `codecall_opt_in`
228
+
229
+ **Use when:**
230
+
231
+ - There is a large toolset but only a **specific subset** should be used via CodeCall.
232
+ - You don’t want all tools in the “code surface”.
233
+
234
+ **Behavior:**
235
+
236
+ - `list_tools`:
237
+
238
+ - Hide all tools by default.
239
+ - Show tools with `codecall.visibleInListTools === true`.
240
+
241
+ - CodeCall index:
242
+
243
+ - Include **only** tools with `codecall.enabledInCodeCall === true`.
244
+
245
+ #### `metadata_driven`
246
+
247
+ **Use when:**
248
+
249
+ - There are **few tools** (≈ up to 10).
250
+ - You want to combine **classic tool calls + CodeCall** on the same tools.
251
+ - The user is comfortable configuring everything via metadata.
252
+
253
+ **Behavior:**
254
+
255
+ - `list_tools`:
256
+
257
+ - Show tools with `codecall.visibleInListTools === true`.
258
+
259
+ - CodeCall index:
260
+
261
+ - Include tools with `codecall.enabledInCodeCall === true`.
262
+
263
+ - No other implicit defaults.
264
+
265
+ ---
266
+
267
+ ## Plugin options
268
+
269
+ ```ts
270
+ export interface CodeCallOptions {
271
+ /**
272
+ * How CodeCall hides/shows tools and includes them in the CodeCall index.
273
+ *
274
+ * - 'codecall_only' (default):
275
+ * Many tools, want them ALL available via CodeCall, but NOT all listed.
276
+ *
277
+ * - 'codecall_opt_in':
278
+ * Many tools, only SOME should be available via CodeCall (opt-in).
279
+ *
280
+ * - 'metadata_driven':
281
+ * Few tools (up to ~10), want to mix normal tool calls + CodeCall
282
+ * and control everything via metadata.
283
+ */
284
+ mode?: CodeCallMode;
285
+
286
+ /**
287
+ * Default number of tools returned from `codecall.search`.
288
+ * @default 8
289
+ */
290
+ topK?: number;
291
+
292
+ /**
293
+ * Maximum number of tool definitions returned from `codecall.describe`.
294
+ * @default 8
295
+ */
296
+ maxDefinitions?: number;
297
+
298
+ /**
299
+ * Optional filter deciding which tools are even *seen* by CodeCall
300
+ * before applying mode/metadata rules. Useful to drop entire groups.
301
+ */
302
+ includeTools?: (tool: {
303
+ name: string;
304
+ appId?: string;
305
+ source?: string;
306
+ description?: string;
307
+ tags?: string[];
308
+ }) => boolean;
309
+
310
+ /**
311
+ * Optional "direct call" mode: allow the LLM to call tools through CodeCall
312
+ * **without** writing JavaScript or running a VM.
313
+ *
314
+ * Exposed via the `codecall.invoke` meta-tool.
315
+ */
316
+ directCalls?: {
317
+ /**
318
+ * Enable or disable direct tool calls via CodeCall.
319
+ * @default false
320
+ */
321
+ enabled: boolean;
322
+
323
+ /**
324
+ * Optional allowlist of tool names that can be called directly.
325
+ * If omitted, a reasonable default is "any tool that is enabledInCodeCall".
326
+ */
327
+ allowedTools?: string[];
328
+
329
+ /**
330
+ * Optional filter for more advanced policies (e.g. based on appId/tags).
331
+ */
332
+ filter?: (tool: { name: string; appId?: string; source?: string; tags?: string[] }) => boolean;
333
+ };
334
+
335
+ /**
336
+ * VM/Enclave configuration and CSP-style policy.
337
+ */
338
+ vm?: {
339
+ /**
340
+ * CSP-like VM preset ('locked_down', 'secure', 'balanced', 'experimental').
341
+ * @default 'secure'
342
+ */
343
+ preset?: CodeCallVmPreset;
344
+
345
+ timeoutMs?: number;
346
+ allowLoops?: boolean;
347
+ maxSteps?: number;
348
+ disabledBuiltins?: string[];
349
+ disabledGlobals?: string[];
350
+ allowConsole?: boolean;
351
+ };
352
+
353
+ /**
354
+ * Sidecar configuration for handling large data in AgentScript.
355
+ *
356
+ * The sidecar allows tool results with large string data to be stored
357
+ * separately and accessed via reference tokens, keeping the script
358
+ * size manageable for security validation.
359
+ */
360
+ sidecar?: {
361
+ /**
362
+ * Enable or disable the sidecar feature.
363
+ * @default false
364
+ */
365
+ enabled: boolean;
366
+
367
+ /**
368
+ * Maximum total size of all stored references (bytes).
369
+ * @default 10MB
370
+ */
371
+ maxTotalSize?: number;
372
+
373
+ /**
374
+ * Maximum size of a single reference (bytes).
375
+ * @default 1MB
376
+ */
377
+ maxReferenceSize?: number;
378
+
379
+ /**
380
+ * Minimum string size to extract to sidecar (bytes).
381
+ * Strings smaller than this stay inline.
382
+ * @default 1024
383
+ */
384
+ extractionThreshold?: number;
385
+
386
+ /**
387
+ * Maximum size when resolving references (bytes).
388
+ * @default 5MB
389
+ */
390
+ maxResolvedSize?: number;
391
+
392
+ /**
393
+ * Allow string concatenation with reference tokens.
394
+ * Set to false (default) for maximum security - prevents
395
+ * attacks like: ref + "__proto__"
396
+ * @default false
397
+ */
398
+ allowComposites?: boolean;
399
+
400
+ /**
401
+ * Maximum script length when sidecar is disabled (bytes).
402
+ * If the script exceeds this length and sidecar is disabled,
403
+ * execution fails with ScriptTooLargeError.
404
+ * Set to null to disable the length check.
405
+ * @default 65536 (64KB)
406
+ */
407
+ maxScriptLengthWhenDisabled?: number | null;
408
+ };
409
+ }
410
+ ```
411
+
412
+ Default `mode` should be `'codecall_only'`.
413
+
414
+ `includeTools` is applied _before_ mode/metadata rules to drop tools entirely from CodeCall.
415
+
416
+ ---
417
+
418
+ ## Sidecar: Handling Large Data
419
+
420
+ The **sidecar** feature enables CodeCall to handle large tool responses without embedding them directly in the script. This is critical for:
421
+
422
+ 1. **Security**: Keeping script size small for reliable AST validation
423
+ 2. **Performance**: Avoiding memory pressure from large inline strings
424
+ 3. **Flexibility**: Allowing tools to return large datasets (API responses, documents, etc.)
425
+
426
+ ### How It Works
427
+
428
+ ```
429
+ ┌────────────────────────────────────────────────────────────────┐
430
+ │ Tool returns large data │
431
+ │ { data: "...1MB of text..." } │
432
+ └────────────────────────────────────────────────────────────────┘
433
+
434
+
435
+ ┌────────────────────────────────────────────────────────────────┐
436
+ │ Sidecar extracts large strings │
437
+ │ "__ref_abc123" ← stored separately │
438
+ └────────────────────────────────────────────────────────────────┘
439
+
440
+
441
+ ┌────────────────────────────────────────────────────────────────┐
442
+ │ Script receives reference token │
443
+ │ const response = { data: "__ref_abc123" } │
444
+ └────────────────────────────────────────────────────────────────┘
445
+
446
+
447
+ ┌────────────────────────────────────────────────────────────────┐
448
+ │ On property access, reference is resolved │
449
+ │ response.data.length → resolves to actual data │
450
+ └────────────────────────────────────────────────────────────────┘
451
+ ```
452
+
453
+ ### Enabling Sidecar
454
+
455
+ ```ts
456
+ const codecall = new CodeCallPlugin({
457
+ sidecar: {
458
+ enabled: true,
459
+ extractionThreshold: 1024, // Extract strings > 1KB
460
+ maxTotalSize: 10 * 1024 * 1024, // 10MB total storage
461
+ allowComposites: false, // Block ref + "string" (security)
462
+ },
463
+ });
464
+ ```
465
+
466
+ ### Script Length Validation
467
+
468
+ When sidecar is **disabled**, CodeCall validates script length to prevent DoS attacks via excessively large scripts:
469
+
470
+ ```ts
471
+ const codecall = new CodeCallPlugin({
472
+ sidecar: {
473
+ enabled: false,
474
+ maxScriptLengthWhenDisabled: 64 * 1024, // 64KB max (default)
475
+ },
476
+ });
477
+ ```
478
+
479
+ If a script exceeds this limit, execution fails with `ScriptTooLargeError`:
480
+
481
+ ```ts
482
+ // Error: Script length (100000 characters) exceeds maximum allowed length (65536 characters).
483
+ // Enable sidecar to handle large data, or reduce script size.
484
+ ```
485
+
486
+ Set `maxScriptLengthWhenDisabled: null` to disable the length check entirely (not recommended for untrusted code).
487
+
488
+ ### Security: allowComposites
489
+
490
+ The `allowComposites` option controls whether reference tokens can be concatenated with strings:
491
+
492
+ ```ts
493
+ // When allowComposites: false (default, secure)
494
+ const result = ref + '_suffix'; // BLOCKED - prevents prototype pollution attacks
495
+
496
+ // When allowComposites: true (less secure)
497
+ const result = ref + '_suffix'; // ALLOWED - use with caution
498
+ ```
499
+
500
+ Keep `allowComposites: false` unless you specifically need string concatenation with large data.
501
+
502
+ ---
503
+
504
+ ## VM presets and security
505
+
506
+ ### Preset type
507
+
508
+ ```ts
509
+ export type CodeCallVmPreset = 'locked_down' | 'secure' | 'balanced' | 'experimental';
510
+ ```
511
+
512
+ ### Expected behavior (intent)
513
+
514
+ #### `locked_down`
515
+
516
+ - Very strict, for sensitive deployments.
517
+ - Short `timeoutMs` (~2000).
518
+ - `allowLoops: false`.
519
+ - `allowConsole: false`.
520
+ - Large `disabledBuiltins` and `disabledGlobals` sets.
521
+ - Analogy: CSP `default-src 'none'`.
522
+
523
+ #### `secure` (default)
524
+
525
+ - Default for untrusted LLM code.
526
+ - `timeoutMs` ~3000–4000.
527
+ - `allowLoops: false` (forces linear code).
528
+ - `allowConsole: true` (logs captured).
529
+ - `disabledBuiltins`: at least `['eval', 'Function']`.
530
+ - `disabledGlobals`: block Node & network, timers (`['require', 'process', 'fetch', 'setTimeout', 'setInterval']`).
531
+
532
+ #### `balanced`
533
+
534
+ - More permissive for trusted/internal usage.
535
+ - `allowLoops: true`.
536
+ - `allowConsole: true`.
537
+ - `disabledBuiltins`: `['eval', 'Function']`.
538
+ - `disabledGlobals`: still block Node/network.
539
+
540
+ #### `experimental`
541
+
542
+ - For local dev only.
543
+ - Higher `timeoutMs`.
544
+ - `allowLoops: true`, `allowConsole: true`.
545
+ - Minimal blocking (still block `eval`, `Function` at minimum).
546
+
547
+ ### Security pipeline
548
+
549
+ 1. **AST validation**
550
+
551
+ - Parse `script` to an AST.
552
+ - Reject:
553
+
554
+ - Disallowed identifiers (from `disabledBuiltins` / `disabledGlobals`).
555
+ - Loop nodes (`ForStatement`, `WhileStatement`, `DoWhileStatement`, `ForOfStatement`, `ForInStatement`) if `allowLoops === false`.
556
+
557
+ - Happens _before_ any VM execution.
558
+
559
+ 2. **VM configuration**
560
+
561
+ - Start from preset defaults.
562
+ - Apply `vm` overrides.
563
+ - Construct `vm2` options with:
564
+
565
+ - `sandbox` object (`callTool`, `getTool`, `codecallContext`, optional `console`, optional `mcpLog` / `mcpNotify`).
566
+ - No `require` or other Node internals.
567
+
568
+ 3. **Execution**
569
+
570
+ - Run script with `vm2` and `timeoutMs`.
571
+ - Map outcomes to `CodeCallExecuteResult`:
572
+
573
+ - `ok`
574
+ - `syntax_error`
575
+ - `illegal_access`
576
+ - `tool_error`
577
+ - `runtime_error`
578
+ - `timeout`
579
+
580
+ ---
581
+
582
+ ## Meta-tool contracts
583
+
584
+ ### `codecall.search`
585
+
586
+ - Input: `CodeCallSearchInput`
587
+ - Output: `CodeCallSearchResult`
588
+
589
+ ```ts
590
+ export type CodeCallSearchInput = {
591
+ query: string;
592
+ topK?: number;
593
+ filter?: {
594
+ appIds?: string[];
595
+ tags?: string[];
596
+ includeOpenApi?: boolean;
597
+ includeInline?: boolean;
598
+ };
599
+ };
600
+
601
+ export type CodeCallSearchResult = {
602
+ tools: {
603
+ name: string;
604
+ description: string;
605
+ appId?: string;
606
+ source?: string;
607
+ score: number;
608
+ }[];
609
+ };
610
+ ```
611
+
612
+ Requirements:
613
+
614
+ - Respect global `topK` default + per-call override.
615
+ - Use the tool index after `includeTools` and mode/metadata filtering.
616
+ - Respect `filter.appIds` to limit by appId when provided.
617
+
618
+ ---
619
+
620
+ ### `codecall.describe`
621
+
622
+ - Input: `CodeCallDescribeInput`
623
+ - Output: `CodeCallDescribeResult`
624
+
625
+ ```ts
626
+ export type CodeCallDescribeInput = {
627
+ tools: string[];
628
+ max?: number;
629
+ };
630
+
631
+ export type CodeCallDescribeResult = {
632
+ tools: {
633
+ name: string;
634
+ description: string;
635
+ inputSchema: unknown;
636
+ outputSchema?: unknown | null;
637
+ usageExamples: {
638
+ description: string;
639
+ code: string;
640
+ }[];
641
+ }[];
642
+ };
643
+ ```
644
+
645
+ Requirements:
646
+
647
+ - Only describe tools that are currently in the CodeCall index.
648
+ - Respect `maxDefinitions` + per-call override `max`.
649
+ - Map internal schemas to JSON-schema-like objects in a stable format.
650
+ - Return up to 5 `usageExamples` per tool (user-provided examples take priority, then smart-generated examples).
651
+
652
+ ### Example Indexing
653
+
654
+ Tool examples are indexed for semantic search with enhanced weighting:
655
+
656
+ - **Example descriptions**: 2x weight in the search index
657
+ - **Example input values**: 2x weight (stringified JSON)
658
+ - **Tool description**: 1x weight (baseline)
659
+
660
+ This ensures tools with relevant examples rank higher in search results. Example generation uses smart intent detection based on tool name patterns:
661
+
662
+ | Pattern | Intent | Generated Example Style |
663
+ | ---------------- | ------ | --------------------------- |
664
+ | `create`, `add` | create | Creates a new entity |
665
+ | `list`, `all` | list | Lists entities with filters |
666
+ | `get`, `fetch` | get | Gets entity by ID |
667
+ | `update`, `set` | update | Updates entity fields |
668
+ | `delete`, `rm` | delete | Deletes entity by ID |
669
+ | `search`, `find` | search | Searches with query |
670
+
671
+ User-provided examples (via `@Tool({ examples: [...] })`) always take priority over auto-generated ones.
672
+
673
+ ---
674
+
675
+ ### `codecall.execute`
676
+
677
+ - Input: `CodeCallExecuteInput`
678
+ - Output: `CodeCallExecuteResult`
679
+
680
+ ```ts
681
+ export type CodeCallExecuteInput = {
682
+ script: string;
683
+ allowedTools?: string[];
684
+ context?: Record<string, unknown>;
685
+ };
686
+ ```
687
+
688
+ Inside the VM, injected globals (conceptual):
689
+
690
+ ```ts
691
+ declare function callTool<TInput, TResult>(name: string, input: TInput): Promise<TResult>;
692
+
693
+ declare function getTool(name: string): {
694
+ name: string;
695
+ description: string;
696
+ inputSchema: unknown;
697
+ outputSchema?: unknown | null;
698
+ };
699
+
700
+ declare const codecallContext: Readonly<Record<string, unknown>>;
701
+
702
+ declare const console: {
703
+ log: (...args: unknown[]) => void;
704
+ warn: (...args: unknown[]) => void;
705
+ error: (...args: unknown[]) => void;
706
+ };
707
+
708
+ declare function mcpLog(
709
+ level: 'debug' | 'info' | 'warn' | 'error',
710
+ message: string,
711
+ metadata?: Record<string, unknown>,
712
+ ): void;
713
+
714
+ declare function mcpNotify(event: string, payload: Record<string, unknown>): void;
715
+ ```
716
+
717
+ > `console`, `mcpLog`, and `mcpNotify` may be disabled depending on configuration.
718
+
719
+ Result type:
720
+
721
+ ```ts
722
+ export type CodeCallExecuteResult =
723
+ | {
724
+ status: 'ok';
725
+ result: unknown;
726
+ logs?: string[];
727
+ }
728
+ | {
729
+ status: 'syntax_error';
730
+ error: {
731
+ message: string;
732
+ location?: { line: number; column: number };
733
+ };
734
+ }
735
+ | {
736
+ status: 'illegal_access';
737
+ error: {
738
+ message: string;
739
+ kind: 'IllegalBuiltinAccess' | 'DisallowedGlobal' | string;
740
+ };
741
+ }
742
+ | {
743
+ /**
744
+ * Unhandled error thrown by the user script itself
745
+ * (not tied to a specific tool call).
746
+ */
747
+ status: 'runtime_error';
748
+ error: {
749
+ source: 'script';
750
+ message: string;
751
+ name?: string;
752
+ stack?: string;
753
+ };
754
+ }
755
+ | {
756
+ /**
757
+ * Error thrown while calling a specific tool via callTool().
758
+ * Includes tool name + input so the agent/LLM can reason about it.
759
+ */
760
+ status: 'tool_error';
761
+ error: {
762
+ source: 'tool';
763
+ toolName: string;
764
+ toolInput: unknown;
765
+ message: string;
766
+ code?: string;
767
+ details?: unknown;
768
+ };
769
+ }
770
+ | {
771
+ status: 'timeout';
772
+ error: { message: string };
773
+ };
774
+ ```
775
+
776
+ ### Tool call notifications & error model
777
+
778
+ #### Automatic notifications for `callTool`
779
+
780
+ Every `callTool()` invocation should produce MCP notifications so the client/agent can observe:
781
+
782
+ - **Before** the tool runs:
783
+
784
+ - “tool call start” notification:
785
+
786
+ - `toolName`
787
+ - `input`
788
+ - `callId` (correlates with end event)
789
+
790
+ - **After** the tool finishes:
791
+
792
+ - “tool call end” notification:
793
+
794
+ - `toolName`
795
+ - `input`
796
+ - `callId`
797
+ - `status` (e.g. `"ok"` / `"error"`)
798
+ - optional `durationMs`
799
+ - optional `error` information if it failed
800
+
801
+ Exact notification shape is defined by the FrontMCP transport, but CodeCall must emit them around each `callTool` execution.
802
+
803
+ #### Script-driven logging & notifications
804
+
805
+ CodeCall can expose FrontMCP logging/notification builtins (`mcpLog`, `mcpNotify`) into the VM so the plan itself can decide what to log/notify:
806
+
807
+ ```js
808
+ async function main() {
809
+ mcpNotify('step_started', { step: 'load_users' });
810
+
811
+ const users = await callTool('users:list', { limit: 100 });
812
+
813
+ mcpLog('info', 'Loaded users', { count: users.items.length });
814
+
815
+ mcpNotify('step_completed', {
816
+ step: 'load_users',
817
+ count: users.items.length,
818
+ });
819
+
820
+ return users.items.map((u) => u.id);
821
+ }
822
+
823
+ return main();
824
+ ```
825
+
826
+ These are **thin wrappers** over core logging/notification APIs and must not bypass sandbox constraints.
827
+
828
+ #### Error separation: script vs tool
829
+
830
+ - **Script-level errors**:
831
+
832
+ - `status: 'runtime_error'`, `error.source: 'script'`
833
+ - Thrown by user code itself (invalid property access, thrown `Error`, etc.).
834
+ - Not tied to a single tool call.
835
+
836
+ - **Tool-level errors**:
837
+
838
+ - `status: 'tool_error'`, `error.source: 'tool'`
839
+ - Thrown while executing a specific `callTool(toolName, input)`.
840
+ - Include:
841
+
842
+ - `toolName`
843
+ - `toolInput`
844
+ - `message` (+ optional `code` / `details`).
845
+
846
+ The LLM can also wrap `callTool` in `try/catch` inside the plan. If it doesn’t, CodeCall surfaces failures as `tool_error` with enough context for retries.
847
+
848
+ Unknown tool / permission issues should typically surface as:
849
+
850
+ - `tool_error` with `code: 'UnknownTool'`, or
851
+ - A tool-defined “authorization required” payload in `details` (for a future auth plugin to handle).
852
+
853
+ ---
854
+
855
+ ### `codecall.invoke` (direct call, no VM)
856
+
857
+ If `directCalls.enabled === true`, CodeCall exposes an additional meta-tool such as `codecall.invoke` that allows the LLM to call a tool **without** writing a JavaScript plan.
858
+
859
+ Type (conceptual):
860
+
861
+ ```ts
862
+ export type CodeCallInvokeInput = {
863
+ tool: string;
864
+ input: unknown;
865
+ };
866
+ ```
867
+
868
+ Behavior:
869
+
870
+ - Validate that `tool` is allowed by `directCalls` policy.
871
+ - Forward the call to the **same tool call pipeline** as normal MCP calls:
872
+
873
+ - PII plugin(s)
874
+ - auth / rate limits
875
+ - logging / audit
876
+ - other lifecycle plugins
877
+
878
+ - No VM is created and no user JS is run.
879
+
880
+ Output:
881
+
882
+ - On success:
883
+
884
+ - Return tool result (or a wrapped `{ status: 'ok', result }`).
885
+
886
+ - On error:
887
+
888
+ - If tool is unknown/not allowed:
889
+
890
+ - Return a clear error referring to `tool`.
891
+
892
+ - If the tool throws:
893
+
894
+ - Return a `tool_error`-like shape including:
895
+
896
+ - `toolName`
897
+ - `toolInput`
898
+ - `message`
899
+ - optional `code` / `details`.
900
+
901
+ ---
902
+
903
+ ## Sessions, caching & tool list notifications
904
+
905
+ CodeCall should integrate with the surrounding transport protocol:
906
+
907
+ - With **session IDs + tool list change notifications**:
908
+
909
+ - Clients cache search/describe results per session.
910
+ - CodeCall emits notifications when tools relevant to a prior search/describe change.
911
+ - Clients can re-describe only when notified.
912
+
913
+ - Without notifications:
914
+
915
+ - Orchestrators should re-run `codecall.describe` before relying on a tool in `codecall.execute`.
916
+
917
+ Implementation-specific details (event names, payloads) live in the transport layer, but CodeCall needs hooks to trigger these events.
918
+
919
+ ---
920
+
921
+ ## Dev workflow
922
+
923
+ Suggested directory:
924
+
925
+ ```text
926
+ libs/plugins/src/codecall/
927
+ ├─ index.ts
928
+ ├─ plugin.ts
929
+ ├─ vm/
930
+ │ ├─ presets.ts
931
+ │ ├─ ast-validator.ts
932
+ │ └─ executor.ts
933
+ ├─ types/
934
+ │ └─ codecall.d.ts # global ToolOptions extension
935
+ ├─ __tests__/
936
+ │ ├─ search.spec.ts
937
+ │ ├─ describe.spec.ts
938
+ │ ├─ execute.spec.ts
939
+ │ ├─ direct-calls.spec.ts
940
+ │ └─ security.spec.ts
941
+ └─ README.md # this file
942
+ ```
943
+
944
+ Typical commands (adapt to repo):
945
+
946
+ ```bash
947
+ npx nx test codecall
948
+ npx nx lint codecall
949
+ ```
950
+
951
+ Testing guidelines:
952
+
953
+ - **Happy path**:
954
+
955
+ - search → describe → execute with valid tools and simple scripts.
956
+
957
+ - **Error cases**:
958
+
959
+ - Script syntax errors.
960
+ - Illegal access (banned globals/builtins).
961
+ - Unknown tool names.
962
+ - Tool-level errors vs script-level errors.
963
+ - Timeout behavior.
964
+
965
+ - **Mode behavior**:
966
+
967
+ - Which tools appear in `list_tools` and search results under each mode.
968
+
969
+ - **Direct calls**:
970
+
971
+ - `codecall.invoke` success + error paths.
972
+
973
+ - **Security tests**:
974
+
975
+ - Attempt access to `process`, `require`, `fetch`, etc.
976
+ - Attempt loops when `allowLoops === false`.
977
+ - Ensure PII plugins still see all inputs/outputs.
978
+
979
+ ---
980
+
981
+ ## Extensibility ideas
982
+
983
+ Safe extension points:
984
+
985
+ - Pluggable search strategies (BM25, embeddings).
986
+ - More granular tagging/filtering of tools.
987
+ - Helper utilities injected as globals (e.g. pagination helpers), guarded by AST + config.
988
+ - Per-tenant / per-session VM policies (based on `codecallContext`).
989
+ - Future **auth/permissions plugin** that:
990
+
991
+ - Detects “authorization required” tool responses.
992
+ - Drives OAuth/permission flows.
993
+ - Retries execution when possible.
994
+
995
+ Any change that alters the LLM contract (field names, tool names, result shapes) should:
996
+
997
+ 1. Be treated as a breaking change.
998
+ 2. Update Mintlify docs and this README.
999
+ 3. Provide migration notes when possible.