Skip to content

User Guide

Overview

vmblu is a collection of tools, formats and prompts designed to build, understand and maintain software created with coding agents.

It serves two roles simultaneously:

  • guiding the coding agent while constructing the system
  • enabling the human to understand, verify and control what has been built

The vmblu toolkit can be roughly divided into:

  • Schemas — formal descriptions of the architecture and the data exchanged between application components.
  • Prompts — structured instructions that define how the coding agent designs and builds the system, including the roles it assumes during development.
  • Commands — mechanical utilities such as project initialization, application generation, test harness creation and source profiling.
  • Graphical Editor — an interactive representation of the architecture as interconnected nodes, allowing navigation, inspection and modification.
  • Runtime — a message-switching layer between components, supporting inspection, sandboxing and enforcement of runtime permissions.
  • MCP support — exposes selected parts of the system as tools for agents, making natural language interfaces straightforward to build.

The toolkit — or scaffold — supports development, but ultimately produces a normal deployable application that can run in a browser, on a server or elsewhere.

Vibe coding is great fun, but vmblu targets applications that are complex and long-lived. Applications you must still understand, extend and verify six months from now.

Central to vmblu is explicit architecture:

  • What are the building blocks?
  • How are they interconnected?
  • What data do they exchange?
  • What is allowed — and what is not?

The coding agent is prompted to design this architecture before writing code. Architecture is not where agents naturally excel, so vmblu makes it explicit, inspectable and continuously aligned with the implementation. The architecture is not documentation — it is the system.

An important component of vmblu is the graphical editor. It provides an immediate overview of the system structure and allows interactive modification: adding nodes, connecting or disconnecting components, inspecting data formats, navigating source code and setting runtime permissions. Although a large part of this user guide focuses on the editor, it is straightforward to use.

vmblu is open source. All formats, prompts and tools are available for inspection and modification.

The vmblu framework

The following paragraphs give an overview of the files, commands and schemas that are used by vmblu.

Files

When a coding agent builds an application, the first command it executes is

vmblu init <project-name>.

This creates the following directory structure and files:

    .vmblu/
        blu.schema.json
        blu.annex.md
        viz.schema.json
        prf.schema.json
        vmblu.prompt.md
        develop.prompt.md
        test.prompt.md
    nodes/
    package.json
    <model-name>.mod.blu
    <model-name>.mod.viz
    <model-name>.src.prf

The .vmblu/ folder

This is where the schemas and prompts for the coding agent are located. It contains the following files:

  • blu.schema.json : the schema description of the architecture file. Source of truth.
  • blue.annex.md : an explanation about the semantics of the architecture file.
  • viz.schema.json : a file generated by the vmblu editor that contains representation details about architecture. Not critical, can be regenerated.
  • prf.schema.json : The schema for the results of a full parse of the source code.
  • vmblu.prompt.md : The main system prompt, explaisn what the agent should read and do.
  • develop.prompt.md : The prompt for an agent that takes the developer role.
  • test.prompt.md : The pompt for an agent that takes the tester role.

These files are common to all vmblu projects.

The project specific files

The following files are specific to a project:

  • <model-name>.mod.blu: The architecture file. It follows the blu.schema file and contains the truth about the project: the nodes, the connections, the contracts, the data types. Everything else is derived from it.
  • <model-name>.mod.viz: The representation details of the model. First version is laid-out automatically by the editor, but the user can change as reauired. The auto-layout is good, but not optimal. It pays off to spend some time rearranging the nodes and the routes. To start over, just delete the file.
  • <model-name>.src.prf: The vmblu editor parse the source code to find where handlres live, messages are being sent, the parameter of handlers etc. The editor and the agent use this file to quickly find their way in the source code and to check that contracts are respected. It is regenerated by the editor when the code of the model changes.
  • <model-name>.app.js: This is the app that is generated from the model file with the command vmblu make-app.
  • <model-name>.mcp.js: This file is alos generated by the command vmblu make-app, if there are messages that have been declared as accessible via MCP.
  • package.json: for completeness we also mention the package file, where the agent can add libs, frameworks tools etc. that the project requires.

The nodes folder

This is where source code for the nodes is located. Simple nodes will probably fit into a single file, but complex nodes or nodes with ui components will need their own directory. The name of the node is used as the name for the file or directory. You can deviate from this convention if your project requires it, but it is a clean convention that reflects the structure of the architecture.

One thing to watch out for when working with coding agents is to make sure they allocate all code they write to nodes. This is given as an explicit instruction in one of the prompt files, but occasionally we have seen agents deviate from that. You can of course explicitely ask to write common libraries if the project requires it, but that should be a deliberate choice.

The test/ folder

An agent taking on the tester role will also create the following files and folders:

    test/
        <model-name>.tst.blu
        <model-name>.tst.viz
        <model-name>.tst.prf
        <model-name>.tst.app.js
        mirrors/

The test files contain a model where each node is paired to a 'mirror' node that has an output for each input of the node and an input for each output. That mirror node allows to test the node. The mirrors/ folder contains the source code for each one of these mirror test nodes.

Commands

The vmblu cli commands are used by the coding agent and/or the editor to create files or folders for the project. One rarely needs to use these commands directly.

The following commands are part of the vmblu cli :

vmblu init <folder-name>

Flags:

  • --name <project> – Project name (default: folder name)
  • --schema <ver> – Schema version (default: latest)
  • --force – Overwrite existing files
  • --dry-run – Show actions without writing files

This command creates files and folders; it does not modify existing blueprints unless --force is used.

vmblu make-app

Flags:

  • --out <file> - Output file (default: <model-name>.app.js)

This command creates the executable app from the blueprint. Can also be created directly from the editor.

vmblu make-test

  • --out <dir> - Output folder (default: ./test)

This command sets up the test structure for the project. It creates the test folder and the test model. The test modesl contains all the source nodes of a project and their mirror nodes (i/o inverted). After this the coding agent still has to write the code for the mirror nodes to do the test required for a node.

vmblu profile

Flags:

--out <file> - specifies the output file (default: <model-name>.src.prf) --full - check all source files in the model --changed <files...> - only check changed files --deleted <files...> - remove data from deleted files --delta-file <path> - write the delta to a file --reason <text> - information

Editor

The editor is the central tool in vmblu. It allows to inspect and modify the architecture of a system. The coding agent knows how to work with the model, so one can simply instruct the coding agent to make the changes required, but manuallly changing the model is perfectly fine.

When a coding agent works with the blueprint it works at the logical level: nodes, i/o contracts , connections etc. and it is the editor who will do the first layout of new nodes and routes. This auto-layout feature is ok, but it does not try to do the best possible auto-layout, so often a user will want to re-arrange nodes and routes to get a more pleasing arrangement. Once you have done that, new nodes and routes are added to that modified blueprint. Layout information of a model is kept in a separate file, <model-name>.blu.viz, and if the file is deleted, the editor will simply auto-layout the whole blueprint again.

You can also perfectly build the architecture of a system manually using this editor, or build part of it and let a coding agent take over afterwards. Some of the examples where built 'manually'.

The rest of this document is mainly an explanantion of how to work with the vmblu graphical editor, but actually it is straightforward. Most actions are accessible via a context menu (with keyboard shortcuts available) and if you have ever worked with node-based systems, you will be up and running in no time.

Work flow

To start of a new project you simply have to instruct your coding agent that you want to use the vmblu framework. As an example, this is the initial prompt for the chat application tutorial in this documentation:

md
We are making a simple chat application. 
It consists of two sub-projects: a chat server and a chat client.

# Steps to make the application

In `./examples/chat-application`, initialize the projects:

  - `vmblu init "chat-client"`
  - `vmblu init "chat-server"`

In order to develop these applications we are going to use the vmblu framework. 
You can find the documents, that you should read carefully before making 
the app in `./chat-client/.vmblu/vmblu.prompt.md`. 
The same documents also exist in the chat server, but you do not have to read 
them twice.

You will make the application in three steps:

  1. first we will make the architecture in the respective model files. 
  Ask for approval after having made the architecture.

  2. When the architecture is approved, write the code for the nodes of the 
  application.In this phase your role is that of a developer or builder.

  3. when the code for the nodes has been written, generate the test model 
  for the application and write the test code for mirror nodes. In this 
  phase your role is that of a test engineer or verifier.

# Description of the application

Here you give a more or less detailed description of your application. 
Focus on architecture first. Also specify the frameworks or runtime 
environment you want (node.js, vite, svelte etc.)

Note that this prompt actually defines two projects (not a problem). With this prompt, we also ask the agent to stop after having made the architecture. We also requested to write test software for the test model. Of course the content part of the application that you are making you have to provide yourself.

The standard prompt files that vmblu is using can be found in the .vmblu/ folder and all end in *.prompt.md. There is one general file where the model will start from, and then two specific ones, one for the role of the agent as a developer and one for the role as a tester. These prompt files can be adapted of course if you want to impose certain wow etc.

Up to now, coding agents are often only so-so at designing an architecture. Mostly not bad, but often they design with too few nodes, or too much io pins etc. We suspect that this is because they did not see many explicit architectures during their trainging phase, but we also expect that they will only become better at it. But more importantly, they respond well to instructions to improve and build on an initial architecture, and when requested to add certain nodes they are very capable at figuring out what type of interfaces the node needs.

Much of the design is going back and forth with the agent to come to an accepted architecture and then letting the coding agent write code. Architecture and code can be reviewed and changed at any point and will always be in step. The coding agent will run the system to check that it behaves as expected and also the human in the loop will want to check it.

The coding agent can be requested to take the role of a tester and write the code for the mirror nodes that will run tests on each individual source node and emit a report. This is explained more in detail in the chapter on testing.

Nodes have security settings that are statically enforced, but the runtime also allows for runtime interception of violations. More about this in the chapter on security.

Core Concepts

Nodes

There are three types of nodes in vmblu. They can be recognized by the leftmost icon in the header of the node.

  • Source node — implemented in code - factory icon
  • Group node — contains a sub-model, behaves as one node externally - group icon
  • Linked node — imported from another model file - link icon - or a bundle - padlock icon

node typesnode types

Click on the...

  • factory icon to see or set the location of the generator function for the node
  • group icon to open the view with the internal layout of the group node
  • link icon to see or set the location of the node linked to
  • padlock icon to see the location of the node linked to

Linked nodes can only be changed in the model they are defined in. The name of the node and the position of the pins, eg for better routing, can however be changed also in the importing model.

The three other icons in the node header are common for all nodes:

  • settings icon: the optional settings can contain a json structure that is passed to the node when it is instantiated and that contains initialisation data for the node. A linked node initially inherits the settings from the original node, but the values can be changed per node. Any changes made to the format of the settings in the original node are propagated to the linked nodes.
  • pulse icon: the pulse icon contains data in json format that is passed to the vmblu runtime but not to the node. It contains information about where to instantiate the node (eg a worker) debugging requests or security settings. The format of the data is determined by the runtime.
  • text icon: the text icon allows to open a field to enter a longer format comment about the node - it can be used as the initial prompt for an LLM to write the code for the node.

Pins & Interfaces

pins and interfaces

A node has pins and these pins are usually organised in interfaces.

Input / Output pins

  • Input - an input pin of a source node maps to a handler of the node: pin do this maps to onDoThis()
  • Output - a node can send a message over an output pin using the transmit function

A pin name of a given type (input or output) must be unique for a node. By design pin names are case insensitive, ie pin names Get File or get file or gET fiLe are considered the same. Note that, as explained below, the interface name is often part of the complete pin name.

Data is sent over a pin using the send function - where tx comes from is explained below :

js
tx.send("pin name", payload);

Pin name or message are used interchangeably in the descriptions below.

Interfaces

Interfaces are named sections grouping pins. If a pin name starts or ends with one of the following characters : . - _ +, then the interface name is added before/after the pin name, except on the node diagram because the interface name is shown above it anyhow. But in the code the messages will be sent over the pin using the full name: tx.send('if-name.rest-of-pin-name', payload).

The + sign stands for a single space that will be added between the interface name and the pin name. As an example: interface name file combines with messages .get, read- and +save as to file.get, read-file and file save as respectively.

Pins and interfaces can be moved left or right, up or down, to improve routing between nodes.

Request / Reply pins

Request/Reply pins are an output/input pair with back-channel. A node can do a simple reply over the back channel to the node that issued the request. Can be recognized by the small circle at the arrow's base.

In javascript the request/reply mechanism uses promises syntax:

js
tx.request("pin name", requestPayload, optionalTimeout)
  .then((replyPayload) => {})
  .catch((error) => {
    /* errors:
            - message: 'Reply timeout' options: {sender, msec}
            - message: 'No channel'
        */
  });

Combining pins on one line

Pins of the same type can be combined in multipins to improve clarity of the circuit diagram. For example clock.[on, off] is a shorthand for two pins clock.on and clock.off. Multipins can be connected if there is a partial overlap: clock.[on, off] can be connected to start.on for example.

Routes

Routes connect pins.

  • Orthogonal routing for clarity.
  • Any output can connect to any input (names don’t need to match).
  • Multi-message pins connect if there is a partial overlap - routes between multipins are wider.
routes
Routes connecting node pins.

Impossible connections (input to input etc.) are not allowed in vmblu. Requests are normally connected to replies, but can be connected to regular inputs, eg for tracking purposes.

Left click on a pin to start a route, shift left click anywhere on a route to undo the route up to that point. The nearest pin will be disconnected.

Routes can be reshaped by clicking on and moving segments.

Clicking on pins and interfaces highlights all the connections for that pin or interface. Also all the connections for a single node can be highlighted (ctrl h)

Buses

To simplify routing between pins, buses can be used. Where for a direct connection between nodes, pin names are not important, buses connect pins based on names: if an output pin abc is routed to a bus and also an input pin abcis routed to that same bus, then these pins are connected.

Create a busbar or cable via the context menu or use ctrl b. For clarity, buses can be given a name.

When a route can terminate on a bus, the bus will light up when passing over it while drawing the route. When releasing the left mouse button, the route will be connected to the bus via a bus tack.

By design, inputs/outputs of the same node cannot be connected via a bus.

routes
Buses

The fact that buses use name based routing is normally not a problem, because the same message names will often be used when connecting interfaces. However if a connection via a bus cannot be done because of a name mismatch then a tack renamer can be used. The match between pins will then be made based on the name in the tack renamer iso the pin name. Make a tack renamer by simply double clicking on the tack arrow.

Pads

Pads are used inside a group node and expose the pins of the group node. There are two ways to add a pad, either by adding a pin to the group node or by extruding a pin inside the group node view. To extrude a pin, shift-ctrl + left click on the pin and move the cursor.

pads

Multiple inputs/outputs can be connected to a pad. Pads can also be connected to buses.

Using the Editor

main menu

  • accept changes : when a linked node is modified in its original file, the model that imports the node will show what has changed - added, changed or deleted pins and interfaces. Additions will be shown in green, removed pins or connections in red. Click on this icon to accept these changes.

  • sync model : changes to external files, eg nodes with external links, will reflect immediately in the importing file. If the node linked to is however in the same file, it can be necessary to do a 'sync' to force an immediate update of the model.

  • set save point / back to save point: before a big modefication of a model it might be worth considering to set a save point, which saves the current status of the file. If the modefication did not turn out as expected, back to save point will reset the model to what was saved. The initial save point is set when the model is read from file.

  • make lib: outputs a file 'my-model-lib.js' that is the main file for building a bundle with the nodes of the model. Also the model is part of the bundle. Typically used to build a library of standard nodes.

  • make app: outputs the main file of the vmblu-based application. To be included in a browser page, run at the server etc. The file consists of a list of nodes and filters that are created by the runtime.

  • settings: the settings for the model.

Selections

The editor often works on selections: a node, a pin, a group of nodes etc.

Selecting in the editor is done by clicking on the node or pin to be selected. To select multiple nodes or pins in a rectangular area use [shift] + [left mouse button] + drag. A selection is shown in orange or with an orange box around it.

To reposition a pin in a node use [ctrl]+[left mouse button]+drag. You can also drag a group of selected pins like that. To drag all the pins of an interface, simply [ctrl]+[left mouse click]+drag the interface name.

selection

Context Menus

The actions that are available for an item of the model can be selected from a context menu, that pops up when clicking the [right mouse button] on the item. For many of the actions listed in the menu there is akos a key combination available. The editor has the following context menus:

Background Menu

background menu

Node Menu

node menu

  • source to slipboard: Puts an outline of the source code for this node on the clipboard. The outline contains the headers for the handlesr of the input pins of the node.
  • make a test node: creates a node that is a mirror image of this node: for every input pin of the node the mirror node has an output pin, and for evey output pin the mirror node has an input pin. The mirror node is typically used to make a test node for the original node.
  • update profiles: re-scan the source of the node to update what parameters are required by a message handler and where output messages are sent.

Pin Area Menu

pin area menu

  • add channel: makes a request pin from an output pin and a reply pin from an input pin.
  • profile: shows the profile of the pin. For an input or reply pin this shows the parameter signature of the handler for the message and the file where the handler can be found. When clicking on the file the file is opened at the position of the handler. For an output or request message this shows the location(s) in the source where the message is sent. Clicking on these opens the file at the location where the message is sent.

Input pin profile example: input pin profile

Output pin profile example: output pin profile

Bus Menu

selection

Selection Menu

selection

  • group: allows to make a single group node from the selected nodes. External connections will stay intact. If needed buses will be duplicated inside the group node. External pins will be given corresponding pads inside the group node.

Source Integration

Every source node has an implementation in code. In that code handlers correspond with input/reply messages and output/request messages are sent via the runtime.

Source nodes are instantiated at runtime by calling the factory function for that node. The factory function can either be a normal function that returns the node, or it can be a class name, in which case the runtime will instantiate with new mySourceNode.

When the factory function is called, two parameters are passed to it, tx and sx, and the function should return, explicitely or implicitely, a reference to an object that allows the runtime to parse for the message handlers. In most cases that object is simply the instance of the node, but it does not have to be.

Factory functions

Below are a few examples of factory functions:

A simple function that returns the node:

js
function mySourceNodeFactory(tx, sx) {
  // create the node here
  const mySourceNode = ...

  // explicitely return
  return mySourceNode;
}

A constructor function:

js
//Constructor for planet
export function CelestialOrb(tx, sx) {
  // save the tx and the sx
  this.tx = tx;
  this.sx = sx;

  // the planet specs
  this.ephemeris = null;

  // etc...

  // return is implicit
}
CelestialOrb.prototype = {
  // The message handlers - and other methods
  onSomeMessage(payload) {},

  onSomeOtherMessage(payload) {},
};

A class:

js
// Declaration
class DatabaseAccess {
  constructor(tx, sx) {
    // Save the transmitter
    this.tx = tx;

    // save the settings
    this.sx = sx;
  }
  // The message handlers - and other methods
  onSomeMessage(payload) {}

  onSomeOtherMessage(payload) {}
}

All nodes are created by the runtime before it starts to switch messages between them. This means that a node can already send messages as soon as it is created, the messages will be placed on the message queue, and do not have to worry about nodes not yet being created.

The sx parameter are the settings for the node as set via the settings icon of the node. The txparameter - the transmitter - is an object that gives the node access to the functions of the runtime library.

Below we have a look at how messages are mapped to handlers and to the tx interface.

Messages and handlers

When a node is instantiated, the runtime will scan the returned object for handlers that correspond to the input pins of the object.

A handler function is a normal function that is called by the runtime with the message payload as the only parameter and this set to the node for which the handler is called.

The correspondence is straightforward: with a message some message corresponds a handler with the name onSomeMessage, ie the message name is preceeded by 'on' and camel-cased.

Message names are allowed to contain special characters and spaces, but these special characters are stripped from the handler name. The editor signals when the handler name for two messages are the same, eg some message and some.message.

The tx interface

The transmitter txthat is passed as a parameter to the node exposes the interface with the runtime. It has the following members:

  • tx.send(pinName, payload) This function sends a message over pin pinName with one parameter, the payload. The payload can be anything, but will be an object in most of the cases. The function returns the number of messages that have been send, so if the pin is not connected it will return 0 (zero).
  • tx.request(pinName, payload, { timeout? }) This function also sends a payload over a pin pinName but has an additional, optional, timeout parameter. The request function returns a promise that resolves when the reply from the connected node(s) comes in. If a request is connected to three pins that can give a reply then three promises will be created. The promise is rejected immediately if no reply pins are connected to the request pin, and also when the timeout is triggered. The default value for the timeout is a setting for the runtime, but typically a few seconds.
  • tx.reply(payload) A node can issue a reply on a reply pin, whereby the payload is returned over the same pin and resolves the outstanding promise at the requesting node.
  • tx.next(partial) In stead of a reply, a node can also answer with next. This will return the payload to the requesting node but create a promise again to wait for the answer on this reply. In this way two nodes can have a conversation by using next in turns until it is terminated by the final reply
  • tx.reschedule() It some cases it can be that a node cannot handle a message because certain conditions have not yet been met. This situation can be solved in several ways, but for convenience a simple function has been added to the tx interface that simply puts the message on the message queue again so that it will be presented again at the next iteration.
  • tx.pin this property is the name of the pin from which the message originated that the handler is treating.
  • tx.select(nodeName).send and tx.select(nodeName).request(...) In a vmblu model a node does not have to know anything about the architecture of the total system to send and receive messages. The select function is an exception to that rule, in so far that it allows to select one particular node among all the nodes that are already connected to the pin. So if nodes A, B and C are connected to pin x of a node then you can select one of the nodes by using tx.select('B').send(pin, payload). This is handy for example when many nodes are connected via a bus in test setup.

JSDoc tags

JSDoc tags are used in the source code to make the pin profile information work. Under the hood vmblu uses ts-morph to scan the source files for parameter info. The parameter info can be taken from typescript type info or, for javascript, from the @param JSDoc tag.

vmblu uses three specific tags to locate information:

  • @node <node name> this tag indicates that the methods/functions in a file are for the node of that name, so that the handlers and the tx.send can be linked to that node. Placing it once at the start of a file is sufficient and it stays valid until the next @node tag in the file.
  • @prompt <text> LLM-friendly description of a handler.
  • @mcp if you put the @mcp tage before a handler, then the handler will be included in the mcp tool description file for the app.

The coding agent will add coding tags as required.

Example of a decorated handler function:

js
/**
 * @prompt Notification that a document was renamed.
 * @node document manager
 * @mcp
 * @param {Object} info
 * @param {string} info.oldName - Previous document name
 * @param {string} info.newName - New document name
 */
onDocRenamed({ oldName, newName }) { /* ... */ }

Making the app

As already explained, the coding agent uses a specific command to generate the app from a model file by using the vmblu make-appcommand.

The app can also be generated from within the editor: in the main menu select the rocket icon to generate the javascript code for the app.

The code generated contains three main sections:

  • Import section: here all the files with the factory functions are imported. Example:
js
// ------------------------------------------------------------------
// Model: 
// Path: solar-system-app.js
// Creation date 9/22/2025, 8:57:19 AM
// ------------------------------------------------------------------

// import the runtime library
import * as VMBLU from "@vizualmodel/vmblu";

//Imports
import { ScreenLayout,
		 HelpersSettings,
		 SolarSystemSettings,
		 SimulationSettings,
		 CamerasSettings,
		 IconMenuHorizontal,
		 LLMChatWindow } from './ui/index.js'
// etc...
import { PlanetaryDistance } from './chart/index.js'
import { McpClientOpenAI } from '../../../core/ai/mcp-client-in-browser.js'
import { McpServerInBrowser } from './mcp-server.js'
  • Nodes section: An array of source nodes and the connections to other source nodes. Example:
js
//The runtime nodes
const nodeList = [
	//_______________________________________________SCREEN LAYOUT
	{
	name: "screen layout", 
	uid: "PzOB", 
	factory: ScreenLayout,
	inputs: [
		"-> menu",
		"-> timeline",

    //etc...

		"-> right side toggle",
		"-> right side add div"
		],
	outputs: [
		"visible start -> update start @ renderer (imdr)",
		"visible stop -> update stop @ renderer (imdr)",
		"canvas -> canvas add @ camera manager (LDdW)"
		]
	},
	//________________________________________________HELPER TOOLS
	{
	name: "helper tools", 
	uid: "whha", 
	factory: HelperTools,
	inputs: [
		"-> grid change",
		"-> axes change"
		],
	outputs: [
		"grid show -> grid set @ helper settings (zBFx)",
		"axes show -> axes set @ helper settings (zBFx)",
		"scene add -> scene add @ scene manager (tKKg)",
		"scene remove -> scene remove @ scene manager (tKKg)",
		"scene dispose -> scene dispose @ scene manager (tKKg)"
		],
	sx:	{
		    "grid": {
          "on": false,
          "color": "0x773333"
		    },
		    "axes": {
		        "on": false,
		        "size": "50"
		    }
		}
	},

Also the optional sx, the parameters for the node, and dx, the parameters for the runtime, are included for each node.

  • Runtime section: Here the function scaffold is called. scaffold instantiates all the nodes and returns a reference to the runtime. Finally the runtime is started.
js
// prepare the runtime
const runtime = VMBLU.scaffold(nodeList, filterList)

// and start the app
runtime.start()

Running an app

A vmblu app can be run like any other js or ts app, for example run inside a browser

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vmblu Browser Version</title>
    <link rel="stylesheet" href="index.css">
  </head>
  <body>
    <script type="module" src="vmblu.js"></script>
  </body>
</html>

Or run as a node.js app

bash
node myVmbluApp.js

Console warnings

The scaffold function can issue the following warnings:

  • ** NO HANDLERS ** The node has input pins but the factory returned no handlers.
  • ** NO HANDLER ** The node has an input pin for which there is no handler.
  • ** NO OUTPUT PIN ** tx.send() is used but there is no corresponding output pin.

The app does continue, it could simply be that some functionality has not yet been implemented.

Using MCP

Introduction

MCP is a standardised protocol that exposes 'tools' offered by an application to an LLM. The way this works is that

  • for the application you create an MCP server, an interface that tells what tools the application has to offer in a standard format.
  • for each LLM you wish the application to interface with you create an MCP client that interfaces with the LLM following the specific requirements of the LLM.

The reason why MCP is such an improvement over previous approaches is that you only have to write the MCP Server once for your application, and the MCP client once for each LLM you wish to use your applications with.

Setting up MCP for use in a vmblu application is, because of the use of nodes and message passing, straightforward.

MCP in vmblu

It is clear that the for the client and server to work properly they have to exchange some information. In vmblu this is easy to set up by using a group node as follows:

MCp group node

The three source nodes are:

  • LLM Chat Window: a user interface window that connects to the LLM. In that window the user can type prompts and will see the replies from the LLM. The look, feel and handling of that window depend on your application. As API keys are best not stored in the app code, this window will generally also offer an interface to set and revoke an API key.
  • MCP Client: This node sets up the connection to the LLM used. It also takes in the prompt from the user and returns the replies from the LLM. In addition it has a connection to the MCP Server node for the application.
  • MCP Server: This node reads the mcp tool file - generated by vmblu as we have seen above - and makes it available for the client.

The way this works is that the MCP client issues a request for a manifest for every MCP Server connected to it. The manifest contains some general information about the version used etc.. Following an acceptable reply, the MCP client sends a request to the MCP Server to obtain the list of tools supplied by this application. The MCP client converts the tool list to the format specific for the LLM, and includes the tool list in the prompt records it sends to the LLM.

When the user types a new prompt, for example change camera settings to ..., the client forwards this to the LLM and if the LLM sees that there is a tool available for the request, it will add a tool call record to its reply to the MCP Client. The MCP client then sensd a tool call message to the MCP server.

The MCP server knows how to execute that tool call. In many cases this will simply be sending a message to a node to get something done, but it can be a more complicated action requiring several messages for example. In any case the architetcure of a vmblu application is very well suited for this type of interaction. When the tool call has been executed, the MCP Server will send the result back to the client to inform the user and LLM as required.

Note that in vmblu, you make a message available as a tool by adding the tag mcp before the handler of the message:

js
/**
 *@mcp
 *@param ...
 */
onMessage(){
...
}

The MCP Server node is also one of the type of nodes where the use of wireless communication via tx.wireless(nodeName).send() is natural as The MCP Server is supposed to know the architecture of the application. The alternative would be to add a pin to the MCP server for every tool used and route it to the node that delivers the service. Doable, but not the most elegant solution.

The vmblu distro contains a an example of an MCP Client node and MCP Server node.

Example of an MCP tool file generated by vmblu:

js
// ------------------------------------------------------------------
// MCP tool file for model: SolarSystem
// Creation date 6/27/2025, 4:40:26 PM
// ------------------------------------------------------------------

export const mcpTools = [
    {
        name: 'camera manager_camera add',
        description: 'Trigger camera add on camera manager',
        parameters: [
            {
                name: 'name',
                type: 'string',
                description: '- the name of the camera.'
            },
            {
                name: 'type',
                type: 'CameraType',
                description: '- perspective or orthographic'
            },
  ...
            {
                name: 'lookAt',
                type: 'import(three).Vector3',
                description: '- point that the camera looks at'
            }
        ],
        returns: '',
        node: 'camera manager',
        pin: 'camera add',
        handler: 'onCameraAdd',
        file: 'C:/dev/vm/app/solar/src/3d/camera-manager.js',
        line: 156
    },
    {
        name: 'camera manager_camera update',
        description: 'Trigger camera update on camera manager',
        parameters: [
            {
                name: 'near',
                type: 'number',
                description: '- New near clipping plane distance.'
            },
...
            {
                name: 'aspect',
                type: 'number',
                description: '- New aspect ratio.'
            }
        ],
        returns: '',
        node: 'camera manager',
        pin: 'camera update',
        handler: 'onCameraUpdate',
        file: 'C:/dev/vm/app/solar/src/3d/camera-manager.js',
        line: 192
    },

Testing with vmblu

There is a special prompt for a coding agent to generate a test environment for a vmblu application.

It instructs the coding agent to run the command vmblu make-test. This command creates a test/ folder and a test model. The test model consists of all source nodes of the original model connected to a mirror node. The mirror node has the same pins as the original node, but with the direction swapped: input becoems output, output becomes input.

The mirror node also has a additional connections that are connected via a bus to the sequencer node. The sequencer node is the node that orchestrates the excution of the tests to be done on the source nodes.

Tests setups can require more complex configurations and these can of course be added to the test setup as required. As the original nodes are imported via links, the test model remains always in sync with the original model.

The coding agent will write the code for the mirror nodes and place it in the mirrors/ folder.

Running the tests generates a report and the coding agent will use the output of that report to correct/improve the code of the original node.

Security

Security is handled in two ways: statically and dynamically.

By default a node has limited capabilities: it cannot wommunicate over the network, cannot access the file system and cannot spawn new processes. Of course sometimes nodes need to have these capabilities, and then they can be set explicitely on a node by node basis. A coding agent will conform to these convention and a second coding agent, instructed explicitely to verify adherence, can go through the code to verify and produce a report.

A second line of defenence is the vmblu runtime. There it is checked in real time that code does respect what is it is allowed to do. Deviations from the security policy are blocked and reported. The report is used to find and correct the culprits.

FAQ

  • Does vmblu use standard -closed- nodes ? No in vmblu the function of a node - the source code of the node - is provided by the developer of the system and there are no 'closed' nodes in vmblu, there is no lock-in. Obviously vmblu is ideal to design a library of standard nodes for all kinds of tasks that can then easily be used across multiple project, but that is entirely up to the dev team.
  • Can the vmblu runtime be modified ? Yes, the runtime is part of the vmblu distro and is also open source. It is short and sweet and if for some reason you want to modify it, you can.
  • TS or JS? Both. TS types are read; in JS, add JSDoc for types.
  • Is vmblu available for other languages For the moment vmblu is only available for TS/JS. In principle there is no limitation with respect to the languages the vmblu model can be applied to. As demand grows, runtimes for other languages will be added.
  • Type safety between pins? Each pin has a contract: the format of the data it needs to see when sending or receiving a message. A pin can either 'own' the contract or 'follow' the contract.
  • When to use request/reply? In most cases request/reply will be used sparingly between nodes. If you see that you have to use it a lot , there is probably a better split possible of functionalities between nodes.
  • Can I reuse nodes? Yes: make a linked node or bundle; accept changes to stay in sync.

Glossary

  • Node — source, group, or linked component.
  • Pin — input/output connection point.
  • Interface — group of pins with shared prefix/suffix.
  • Route — connection between pins.
  • Bus — name matched bus.
  • Cable — name-matched bus.
  • Pad — group node IO exposed internally.
  • Profile — pin metadata linking to source code sites.
  • sx/dx — node vs runtime settings.
  • MCP — Model Context Protocol for tool integration.

Appendices

Keyboard Shortcuts

ActionShortcut
Create busbarCtrl + B
Create cableCtrl + K
Multi-selectShift + drag
Move pin/interfaceCtrl + drag
UndoCtrl + Z
RedoCtrl + Shift + Z

JSDoc Cheat Sheet

js
/**
 * @prompt Notification that a document was renamed.
 * @node document manager
 * @mcp
 * @param {Object} info
 * @param {string} info.oldName - Previous document name
 * @param {string} info.newName - New document name
 */
onDocRenamed({ oldName, newName }) { /* ... */ }