Velocitron Simulator MCP Server
README.md
# Velocitron browser simulator
The standalone simulator edits, compiles, lays out, and structurally explores
`.petrinet` source in the browser. Its Node process serves the built application,
provides bounded Graphviz layout, and—when explicitly enabled—hosts a local MCP
endpoint that lets an agent inspect and replace the source open in a live browser
page.
The source-control design extends
[ADR 0021](docs/adr/0021-browser-simulator-seams.md) without moving its
seams: the browser remains the source, compiler, diagnostic, revision, and
simulation authority. See
[ADR 0022](docs/adr/0022-browser-simulator-live-source-control.md) for the
live-source-control decision.
When started with a workspace root, the server additionally exposes that
directory's `.petrinet` files to the page as opaque bytes — listing, reading,
saving, and watching them — while the browser keeps all interpretation,
including multi-file composition simulation. See
[ADR 0024](docs/adr/0024-simulator-file-backed-workspace-mode.md) and
[File-backed workspace mode](#file-backed-workspace-mode) below.
## Status: 0.1.0 alpha
This 0.1.0 alpha is a serious working release for technical exploration, not
a compatibility or production-readiness promise. The application, its APIs,
its MCP surface, and its behavior may change without notice. There is no
migration, maintenance, support-response, or security-support guarantee.
Do not use the simulator for production, security-sensitive, or otherwise
high-consequence workloads. Support is best-effort only.
The simulator consumes the released [`@velocitron/core`](https://github.com/circuitmeridian/velocitron-core)
npm package, pinned to the exact version `0.1.0`. The pin is bumped
explicitly per core release; compatibility across other core versions is not
promised.
## Requirements
- Node.js 20.19.0 or newer (repository development uses Node 24.15.0 or
newer and pnpm 11.13.1).
- pnpm (via `corepack enable`; the repository pins `pnpm@11.13.1`).
- Graphviz `dot`, exactly version 15.1.0, on `PATH` for production layout and
the server test suite. The server refuses other Graphviz versions rather
than producing unverified geometry; see
[ADR 0021](docs/adr/0021-browser-simulator-seams.md). MCP source control
does not invoke Graphviz or add any other compiler/runtime requirement to
Node.
Install the dependencies from the repository root:
```sh
corepack enable
pnpm install
```
## Run the simulator
### Development
Without MCP source control:
```sh
pnpm run dev
```
Vite prints the browser URL. The simulator compiles source in the browser; the
development server supplies the existing layout route.
To enable live source control, set a bearer token before starting the same
development command:
```sh
VELOCITRON_MCP_TOKEN='replace-with-a-long-random-token' \
pnpm run dev
```
Use the loopback URL printed by Vite and append `/mcp` for the MCP server URL.
The live-source routes are disabled when `VELOCITRON_MCP_TOKEN` is absent; the
ordinary browser simulator and layout route continue to work.
### Production
Start the production server with MCP source control enabled (`run start`
builds the browser bundle and the Node server first, so it always serves the
current sources; `run start:prebuilt` skips the build and serves the existing
`dist/`):
```sh
VELOCITRON_MCP_TOKEN='replace-with-a-long-random-token' \
pnpm run start
```
To open a directory of `.petrinet` files instead of the single hash-persisted
editor, pass a workspace root as the first argument (or set
`VELOCITRON_WORKSPACE`, which is also how `run dev` enables it under Vite):
```sh
pnpm run start ./tests/browser/fixtures/workspace
```
The default application URL is `http://127.0.0.1:4173`, and the default MCP URL
is therefore:
```text
http://127.0.0.1:4173/mcp
```
`PORT` may select another port. The MCP-enabled server must remain on a loopback
address; do not expose it through `HOST=0.0.0.0`, a reverse proxy, a tunnel, or a
container-published network port. Plain HTTP is acceptable only on loopback.
## Connect an MCP client
The server uses Streamable HTTP from `@modelcontextprotocol/sdk` 1.29.0. It is
not a stdio server: configure a URL and an HTTP `Authorization` header rather
than a command to launch.
MCP client configuration formats differ, but the equivalent generic settings
are:
```json
{
"mcpServers": {
"velocitron-simulator": {
"url": "http://127.0.0.1:4173/mcp",
"headers": {
"Authorization": "Bearer <the VELOCITRON_MCP_TOKEN value>"
}
}
}
}
```
Some clients call the transport `http` or `streamable-http`; some substitute
environment variables in header values. In all clients, the load-bearing
settings are:
- Streamable HTTP, not the deprecated HTTP+SSE transport;
- the exact `/mcp` URL on the simulator's loopback origin; and
- `Authorization: Bearer ...` with the same value passed as
`VELOCITRON_MCP_TOKEN` at server startup.
The v1 transport is stateless. Successful POST operations use the stable SDK's
per-request SSE response framing so a disconnected client cancels pending work;
this is not a subscription channel. The server does not return or require an MCP
`Mcp-Session-Id`, does not provide resumable streams, and returns HTTP 405 for an
unsolicited MCP GET because subscriptions and server-initiated notifications are
not implemented. Simulator page identity is instead an explicit tool or
resource argument, described below.
`VELOCITRON_MCP_TOKEN` must be strict UTF-8 and at least 32 bytes; an invalid
configured value fails server startup rather than silently disabling MCP. A
missing or incorrect request bearer token is an authentication failure. Treat
the token as a secret: do not commit it, put it in a share URL, paste it into
source, or enable HTTP debug logging that records request headers.
## Browser authority and the Node mirror
Each open simulator page generates an opaque `sessionId`. It is a page-lifetime
identifier, not a path, project ID, durable session, or MCP transport session.
The browser owns:
- the exact editor source;
- browser-local `.petrinet` compilation and canonical diagnostics;
- source revision installation;
- the current contribution state and retained last-valid snapshot; and
- marking, history, structural firing, rewind, and view state.
Node keeps only an ephemeral in-memory mirror so the page and MCP clients can
rendezvous. The mirror is keyed by `sessionId`, disappears when the server
restarts or the page expires, and never reads or writes a `.petrinet` file. It
does not discover projects, call `compilePetrinetText`, validate DSL, create PN
diagnostics, lay out MCP source, or run simulation.
The browser talks to that mirror through a private application long-poll bridge:
- `GET /api/source-control/config` discovers whether source control is enabled;
- `POST /api/source-control/poll` publishes the page's current snapshot and
waits for a pending refresh or complete-source replacement command; and
- `POST /api/source-control/disconnect` removes the ephemeral page session on
unmount or page close, with idle expiry as a fallback.
Those routes are internal browser/server protocol. They are not MCP resources,
tools, or client APIs. Generic MCP clients connect only to `/mcp`.
## Version model
Every accepted source has one complete version identity:
```json
{
"sessionId": "0e6337b2-e69d-4db0-bf49-c26c74b5409d",
"revision": 12,
"sha256": "lowercase SHA-256 of the exact UTF-8 source bytes"
}
```
- `sessionId` prevents using a version from one page against another page.
- `revision` increases monotonically within that page lifetime.
- `sha256` binds the version to the exact source bytes, including whitespace,
line endings, and Unicode.
Do not treat `revision` alone as a write precondition and do not normalize source
before hashing or replacing it.
## MCP resource
Each live page exposes its exact source text at:
```text
velocitron://simulator/{sessionId}/source
```
The resource MIME type is `text/plain; charset=utf-8`, and its content is source
text only. Use `list_simulator_sessions` to discover page IDs and
`read_petrinet_source` when an agent also needs the complete version token,
source ID, UTF-8 byte count, and browser build status. A session may disappear
between listing and reading; list again rather than assuming durable recovery.
The build status is independent of replacement acceptance. Expected states
include `idle`, `compiling`, `awaiting-geometry`, `invalid`, `operation-failed`,
and `ready`. A status other than `ready` does not make the source unreadable.
## MCP tools
The approved v1 tool surface is deliberately small:
### `list_simulator_sessions`
Lists live-page summaries with source ID, UTF-8 byte count, build status, and
complete version. It has no input fields.
### `read_petrinet_source`
Reads one exact current source snapshot.
```json
{
"sessionId": "opaque page session id"
}
```
Read this immediately before calculating a replacement. Preserve the returned
source exactly unless the intended edit changes it.
### `replace_petrinet_source`
Attempts a complete-source optimistic replacement.
```json
{
"sessionId": "opaque page session id",
"expectedVersion": {
"sessionId": "the same page session id",
"revision": 12,
"sha256": "the hash returned by the read"
},
"source": "the complete desired .petrinet source"
}
```
This is replace-if-current, not patch, edit-range, append, merge, or save. All
three fields of `expectedVersion` must match before the command is queued and
again before it is delivered. The browser applies the exact replacement text and
allocates the next revision; Node hashes the exact published source and returns
the resulting complete version after browser acknowledgement. The result status
is `applied` when a new revision was created and `unchanged` when the submitted
source already matched exactly. The server never infers a merge and never
silently overwrites a newer editor revision.
MCP clients may use either the resources or the corresponding read tools. Use
the typed tools when an agent needs an explicit read-before-write flow; use
resources when the client naturally browses contextual state.
## Compile success is separate from replacement acceptance
A successful `replace_petrinet_source` call means only:
1. the complete expected version matched; and
2. the replacement was accepted as the page's exact current source.
It does **not** mean that the source compiles, describes a standalone net, has
valid geometry, or can currently be simulated. The browser applies the source
first and runs its normal local compile/layout pipeline afterward. This permits
normal multi-step editing through temporarily invalid source.
After replacement, call `read_petrinet_source` again until its browser build
status for the accepted version is no longer pending. If it reports `invalid`, inspect the browser-owned compiler
result, prepare another complete source, and replace against the newest version.
The last valid canvas may remain visibly retained in the browser, but it is not
the current source and MCP never reports it as such.
## Conflict recovery
A person may edit the page, or another MCP client may replace it, after a client
reads. In that case `replace_petrinet_source` returns a conflict and the current
version. Recover without blind retry:
1. Call `read_petrinet_source` again.
2. Compare the newly returned complete source with the source previously read.
3. Reconcile the intended change into the new complete document.
4. Retry `replace_petrinet_source` with the new complete `expectedVersion`.
Do not resend the old expected version, drop the hash, or submit only the changed
fragment. If the page session no longer exists, list sessions and ask the user
which live page to target; a new page has a different `sessionId` even if it
opens identical source.
## File-backed workspace mode
Starting the server with a workspace root (CLI argument or
`VELOCITRON_WORKSPACE`) enables workspace mode; without one the mode is
entirely absent and the simulator behaves as described above. The design is
recorded in [ADR 0024](docs/adr/0024-simulator-file-backed-workspace-mode.md).
In workspace mode the page shows a flat file list of every `*.petrinet` file
under the root (recursive; files in subdirectories display their root-relative
path; dot-entries, `node_modules`, and symlinks are skipped). Selecting a file
loads it into the editor and simulator. Edits autosave the complete file after
a short debounce. External filesystem changes — created, modified, and deleted
files — are reflected live: the list updates, an externally modified open file
reloads, and a deleted open file keeps its buffer visible with a notice while
autosave stops so the file is not recreated. The app offers no file creation,
rename, or delete.
Node's role is bytes only. The browser bridge under `/api/workspace/`
(loopback, same-origin, internal application protocol — not MCP) lists, reads,
saves, and watches files; Node never compiles, parses, or otherwise interprets
their contents. Change detection rescans and diffs content hashes rather than
trusting watch events; self-saves are suppressed from the event stream by
hash. Concurrent internal and external writes are last-write-wins, accepted
for a loopback single-developer tool.
Workspace mode also enables multi-file composition simulation: when the open
file compiles to a composition, the browser resolves each `use` ref
source-relative against the workspace, compiles the referenced nets, merges
them with the place-fusion engine from `spec/composition.md` (ported to
`@velocitron/core`), and simulates the merged standalone net. Nested
compositions are not supported. Without a workspace root, composition
documents remain unsupported exactly as before.
## V1 boundaries
The live-source MCP interface controls source text only. V1 intentionally does
not provide:
- stdio transport;
- resource subscriptions, server-initiated notifications, or SSE replay;
- filesystem paths, project discovery, opening/saving files, persistence, or git
operations;
- Node-side `.petrinet` compilation, validation, diagnostics, or retained
snapshots;
- patches, line/range edits, or server-side merging;
- marking selection, fire/run, rewind, layout, named-view, or other simulation
controls;
- recovery of page sessions after server restart; or
- remote or multi-user hosting.
Use ordinary repository tools for filesystem work and the browser UI for
simulation controls. Adding any of the deferred surfaces requires a separate
contract rather than overloading complete-source replacement.
## Feedback and contributions
Open an example net or model a small process of your own, then share what
feels clear, awkward, missing, or misleading. Send broad reactions through an
existing direct channel with the author. Report reproducible defects through
[GitHub Issues](https://github.com/circuitmeridian/velocitron-simulator/issues).
External code contributions and pull requests are not accepted yet. These
feedback routes do not create a commitment to review, merge, respond, provide
support, or maintain compatibility. See
[CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md) for the
current boundaries.
## License and authorship
The Velocitron browser simulator is released under the
[MIT License](LICENSE).
Matthew R. Scott is the sole author and copyright holder. Henrique Bastos is
acknowledged for collaboration toward an interoperable Petri-net
specification; that acknowledgment does not indicate shared authorship.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues