
Security News
Lovable’s OJ Rewrites Vite’s Dev Server in Rust as AI Lowers the Cost of Forking Open Source
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.
convexity-mcp
Advanced tools
MCP server for Convexity energy models — connect Claude, ChatGPT, or any MCP client to a local Convexity model .db.
A standalone MCP stdio server that exposes the
in-app agent's tool set over a local Convexity model .db, so users can drive
Convexity from their own Claude / ChatGPT subscription — no running app, no
Python, no pyconvexity distribution.
It reuses the shared command layer verbatim: mcp/nodeBackend.ts substitutes
for @/platform/backend (the same substitution vite.config.ts makes for
desktop/web), routing every tool invoke() to runCommand() over
better-sqlite3. ~80 DB-pure tools from src/platform/agent/core/tools/ are
exposed; deliberately excluded:
show_on_map, create_chart, ask_user, navigation) —
need a running app. (set_dashboard_config IS included — it writes the
layout into the model file.)run_python, run_shell_command, sidecar import
pipeline) — sandbox concerns / need the desktop sidecar.MCP-only additions on top of the shared set:
get_open_model / open_model — report/switch the target .db (absolute
path; refuses nonexistent files).create_model — create a blank model at a new absolute path (time axis +
default carriers) and make it active; enables build-from-scratch sessions.use_scenario — sets the active scenario for the session.get_timeseries_data — real data points (ISO timestamps + values),
downsampled via the shared resampler, ≤2000 points per call — for charting
and analysis in the client.preview_model_map — renders the network (buses at lat/lon, lines/links)
to a PNG returned as inline MCP image content; highlight names after edits.inspect_data_file / load_timeseries_from_file — CSV/Excel ingest
(shared with the in-app agent, builder/dataFiles.ts): inspect columns,
types, and the time axis, then load one column onto N components, an
explicit column→component mapping, or auto-match headers to component
names. Timestamped data is aligned to the model time axis (naive-as-UTC);
finer data is mean-downsampled, coarser data is refused, gaps follow
fill_missing.cloud_solve_machines / cloud_solve_submit / cloud_solve_status /
cloud_solve_logs / cloud_solve_fetch_result / cloud_solve_cancel —
cloud solve through convexity-api with the cxk_ key from
convexity-mcp login (see Cloud solve).open_in_app / app_state / show_in_app / show_on_map /
show_in_table — open a model in the Convexity desktop app (starting it
if needed), see what it has loaded, and move its views. While the app is
running, edits made here go through it and appear live (see
Driving the Convexity app).solve_via_desktop_app / desktop_app_jobs / desktop_app_job_logs —
route a local solve into the running Convexity desktop app's Job Queue
(see Solving through the Convexity app).modelverse_list / modelverse_get / modelverse_download — browse the
published Modelverse catalogue and bring a model file down to this
computer (default ~/models, the app's model folder), opening it.playbook_list_mine / playbook_sign / playbook_run_submit /
playbook_run_status / playbook_run_logs / playbook_run_outputs /
playbook_run_cancel — sign playbook source as your organisation's and
run signed playbooks in the cloud against the open model (uploaded like a
cloud solve) or a Modelverse model; outputs come back as inline scalars
and downloadable files.playbook_link_publish / playbook_link_set_enabled /
playbook_link_versions / playbook_link_repoint — publish a playbook
saved in the open model as a shareable page and manage the link.import_via_desktop_app / export_via_desktop_app — NetCDF, PyPSA
CSV, Parquet and Excel through the running app's Job Queue.account_status — which cloud features the organisation has enabled.get_capacity_factors,
get_historical_demand, global outlook, reference costs, search_docs),
authenticating with the API key (see Data tools).create_components_batch, delete_components_batch,
set_timeseries_batch (constant / pattern / scaled copy), get_components_table,
create_scenarios_batch, create_sweep, run_batch (a planned sequence of
any tools in one round trip, optionally atomic), import_components_from_file
and create_model_from_spec (see Batching).list_more_tools — names the tools beyond the default set, by area (see
Toolsets).compare_with_actuals — modelled vs the reserved actuals system scenario
for one attribute: aligned fit statistics (Pearson r, MAE, RMSE, bias) at
native resolution + a downsampled paired series for charting. The server
instructions explain the actuals convention so clients stop misreading it
as a normal scenario.instructions teach clients the working discipline (orient cheaply,
batch, never open the file with local apps, chart/map instead).PRAGMA wal_checkpoint(TRUNCATE) so the bare
.db file on disk is always complete.<file>.bak-<timestamp> beside it; the newest 3 are kept.lsof on macOS/Linux, a rename probe on Windows) opens READ-ONLY — close
it in the Convexity app and re-run open_model to edit.--read-only flag: force every model read-only for the process.get_open_model reports writability and the reason when read-only.CONVEXITY_MCP_TOKEN=<long-random-string> \
node mcp/dist/server.cjs /path/to/model.db --http 3948
cloudflared tunnel --url http://localhost:3948 # gives a public https URL
Serves Streamable HTTP on 127.0.0.1 only; refuses to start without the token
and 401s requests missing Authorization: Bearer <token>. Note claude.ai
custom connectors can't attach custom headers — if header auth doesn't fit,
switch to a secret URL path.
npm install -g convexity-mcp # or: npm i -g ./convexity-mcp-<v>.tgz
convexity-mcp # stdio MCP server, no model bound
Ships a single bundle plus better-sqlite3/@resvg/resvg-js as ordinary
dependencies — their official prebuilds cover macOS/Windows/Linux, so no
compiler is needed. Node >= 20.
npm run mcp:build # → mcp/dist/server.cjs
npm run mcp:pack # → mcp/dist/bayesian-convexity-mcp-<v>.tgz
npm run mcp:e2e # protocol e2e against the built bundle
node mcp/dist/server.cjs /path/to/model.db # run from the worktree
Worktree-only caveat: the repo postinstall compiles better-sqlite3 for
Electron — run npm rebuild better-sqlite3 before running the server from
the worktree (and re-run postinstall before Electron dev). The packaged
install has its own dependency tree and never needs this.
{
"mcpServers": {
"convexity": {
"command": "node",
"args": ["/path/to/convexity-js/mcp/dist/server.cjs"]
}
}
}
The model path argument is optional (and best omitted): without it the server
starts with no model bound, and each conversation opens or creates one via
open_model / create_model. Pin a path only for a single-model setup:
{
"mcpServers": {
"convexity": {
"command": "node",
"args": ["/path/to/convexity-js/mcp/dist/server.cjs", "/path/to/model.db"]
}
}
}
--read-only — every model opens read-only for the process.--allow-dir <dir> (repeatable) — restrict open_model/create_model
(and any tool that switches model by path) to the given directories.
Without the flag the server opens any path the OS lets it read.--verbose — log each tool call (name + duration) and full error stacks
to stderr.--http <port> — Streamable HTTP mode (see above).convexity-mcp login # opens the browser; key lands in ~/.convexity-mcp/
convexity-mcp status # where the key came from + live access verdict
convexity-mcp logout # remove the stored key
login starts a loopback listener, opens
convexity.bayesian.energy/connect-mcp, and receives an API key minted
under your Convexity account (the gh/gcloud pattern; a state nonce ties the
callback to your process). The key is stored in
~/.convexity-mcp/credentials.json (0600). CONVEXITY_API_KEY in the
environment always overrides the stored login — mcpb/scripted setups keep
working unchanged.
The cloud tools call api.bayesian.energy (CONVEXITY_API_URL overrides)
with the resolved API key; without one they refuse and point at
convexity-mcp login. The account needs cloud solve enabled, MCP access
enabled, and balance — the API's refusals are relayed in plain words. The
wire client is shared with the app
(src/convexity/frontend/services/cloudSolveApi.ts), so the request shapes
cannot drift.
cloud_solve_submit checkpoints the open model, uploads it gzipped to the
signed URL, submits, and returns a job_uuid immediately. Solves are
billed per hour of machine time; the server instructions make the client
quote cloud_solve_machines prices first, and sizes above s need
confirm_cost: true.cloud_solve_status / cloud_solve_logs poll. Without a job_uuid,
status lists the jobs submitted from this machine
(~/.convexity-mcp/cloud-jobs.json, newest 20).cloud_solve_fetch_result downloads a SOLVED job to a non-colliding
<model>_solved.db beside the source (desktop-app behaviour — the
original is never overwritten) and makes it the active model.When the desktop app is running, a cloud solve submitted here is announced to it over the bridge and appears in its Jobs panel with the model association, so "Open results" works there exactly as for a solve the app submitted. The post-solve email links to the web app; a job the browser never saw is handed over as a downloaded result file rather than refused.
open_in_app returns only once the app confirms the model is open (the
renderer reports what it has open; an open asked for while the app is still
starting is replayed once the workspace mounts). If nothing is confirmed
within 20 s it says so and points at app_state — it never claims an open
that has not happened.
When the desktop app is running it advertises a loopback control surface in
<userData>/mcp-bridge.json (alongside the solve backend; owner-only; trusted
only after a pid check and a live probe). Through it:
open_in_app opens a model in the app and brings it to the front —
starting the app first if it is not running (open -a Convexity, the
per-user install on Windows, convexity on Linux; CONVEXITY_APP_COMMAND
overrides). app_state reports what it has loaded.<file>.bak-<ts> (taken through the app).
get_open_model says when this mode is active; if the app quits, editing
continues on the file directly. A model the user opened by hand in the app
is adopted the first time a command needs one, so "run the playbook" works
without an open_model here.show_on_map, show_in_table and show_in_app move the app's views (the
same navigation the in-app agent performs).Apps from before the control surface (0.9.x) still support
solve_via_desktop_app; the tools say when an update is needed.
The browser build is driven over a loopback connection, on your own computer:
the server listens on a WebSocket (the first free port of 47831–47840;
CONVEXITY_MCP_LOCAL_PORT pins one) and a paired tab connects to it. Nothing
about the tab, the commands or the model travels over the internet, and a
command takes milliseconds. CONVEXITY_MCP_TARGET=auto|desktop|web chooses
when both could answer (auto: the desktop app if it is running, else a paired
tab, else file mode).
Turn on Assistant control in the tab's status bar (off by default, remembered per browser, a dot shows the connection), then pair once:
open_in_app opens the web app (CONVEXITY_WEB_URL,
default the production app) with the pairing in the URL fragment, and the
tab asks you to confirm; orapp_state gives into the Assistant control
popover.The pairing is kept in ~/.convexity-mcp/web-channel.json (owner-only) and in
the tab. Origins are checked: the app, its previews, localhost, plus
CONVEXITY_WEB_ORIGINS.
Assistant control needs Chrome. The tab has to open a connection to your own computer, and only Chrome allows it: it asks once to let the site reach the local network (Chromium 147+ applies that to WebSockets) — say yes. Firefox and Safari refuse, and there Assistant control cannot be used at all; use Chrome, or drive the desktop app instead.
What differs on web, and the tools say so:
open_in_app takes a name (as the tab's Files panel
shows it), create_model creates there, open_model with a path that does
not exist locally opens the tab's model of that name.solve_via_desktop_app submits one through the tab
(billed; machine_size xs or s), watches it, and has the tab load the
result back with wait: true. There is no local queue, so playbooks run
in the cloud and import/export through the app are not available.~/.convexity-mcp/web.When the desktop app is running it advertises its backend sidecar in
<userData>/mcp-bridge.json ({port, token, pid, version}, owner-only;
~/Library/Application Support/Convexity, %APPDATA%\Convexity, or
~/.config/Convexity; CONVEXITY_MCP_BRIDGE overrides the path). The
server trusts the file only after the pid is alive and /health answers,
so a hard-killed app's stale file reads as "not running".
solve_via_desktop_app queues one job per scenario in the app's Job Queue
with the same payload the Run button sends, authenticated with the
sidecar's bearer token. The job appears in the app's Jobs panel within
~2 s (with the app's own "Solve started" toast) and the worker writes the
results into the model file in place — read them with the results
tools once desktop_app_jobs reports completed; there is no download
step. It works on a model that is also open in the app (the usual case;
direct MCP edits to such a model are read-only, but the app's worker may
write). Do not edit the model while its solve runs: two writers on one
SQLite file.desktop_app_jobs lists the queue (or one job); it is also the cheap
"is the app running?" probe. desktop_app_job_logs tails a job's worker
log.cloud_solve_submit.Two verb-style tools: modelverse (list, get, download — a downloaded
model opens in the app when it is running) and playbook. A playbook is
Python stored in a model; the chain, free first, is validate → save (into
the open model, through the running app's solver process so pyconvexity does
the validation and schema work) → run_local (the app's runner, outputs in
the reply) → sign → run (cloud, paid; by playbook_uid once signed here)
→ publish_link (a page at /playbook/<slug>). The authoring contract is the
convexity://playbooks/contract resource and the write_playbook worked
example. Set CONVEXITY_MCP_TOOLSET=full to advertise the fourteen original
tools these stand in for.
The in-app agent's data tools run here unchanged. They authenticate through
getAuthHeader(), which the app backs with Firebase; the MCP build aliases
src/lib/firebase to mcp/firebaseStub.ts (the bearer is the cxk_ key)
and @/config to mcp/config.ts (API_BASE_URL = CONVEXITY_API_URL).
Each is gated per organisation on the API side (renewables_api_enabled
and friends; account_status lists them); refusals are relayed as with the
other cloud tools.
playbook_link_publish mirrors pyconvexity's publish_playbook: it reads
the named playbook (body, input schema, defaults) from the open model's
network_playbooks table, signs the body, uploads a gzipped snapshot of the
model content-addressed by its sha256, and mints the link — idempotent per
playbook lineage, so re-publishing updates the page behind the same URL.
Quotas, price and grants are staff-console routes and are not exposed.
The shared tool set creates one component, one series or one scenario per
call. The batch tools compose those tools by name — same validation, same
backend commands, nothing new touches the database — so a model is a handful
of calls: create_components_batch takes any mix of types with any static
attributes (carriers first, then buses, then the rest, whatever the item
order), set_timeseries_batch accepts constant, a tiled pattern or a
scaled copy so the model never ships 8760 numbers, create_scenarios_batch
and create_sweep build a sensitivity set at once, get_components_table
reads attributes across components as one table, and run_batch executes a
planned list of any tools in one round trip — atomic: true snapshots the
open model first and restores it if a step fails (file mode; when edits go
through the running app the tool reports how far it got instead).
import_components_from_file and create_model_from_spec build from a
spreadsheet or a JSON spec.
Most MCP clients send every tool schema to the model on every turn; the
full catalogue is ~105 KB (about 26k tokens), and Claude Desktop re-sends every
advertised schema on every turn while ignoring tools/list_changed. So the
server advertises the demo-path core by default — app control, model,
batch build, solve and wait, dashboard and views, results, scenarios,
examples, docs search, cloud solve, and the modelverse / playbook tools
when the session starts signed in: 36–38 tools, about 7k tokens.
list_more_tools (build, analyse, app, data) names an area's tools and
widens the list for clients that refresh; any registered tool runs when called
by name, advertised or not. CONVEXITY_MCP_TOOLSET=full advertises everything
(and registers the fourteen original Modelverse, playbook and link tools the
two façades stand in for).
Every bus, generator, load and storage unit needs a latitude and longitude: the app's map is the primary view, and the create tools (single and batch, spec and file import) refuse a point asset without one. Links and lines take their ends from their buses.
The server publishes the MCP prompts capability: prompts/list returns the
worked examples in mcp/prompts.ts (build a three-bus network and view it in
the app, load demand from a CSV, solve and plot, describe a model, compare a
scenario with the base case, sweep a parameter, make a generator committable)
and prompts/get renders one with its arguments. Claude Desktop and Claude
Code surface them as prompt templates, so a new user can run a complete
example without typing anything but a file path. Clients that never fetch
prompts get the same text through get_worked_example and the
convexity://examples/<name> resources. The same texts drive the benchmarks
below and the example pages in the user docs, so they cannot drift apart.
The server instructions carry the tool order that works, so an agent does not have to discover it:
create_model → create_components_batch
(buses first, with coordinates) → set_timeseries_batch → open_in_app →
solve_via_desktop_app({wait: true}) → get_solve_results /
get_summary_stats / get_prices → preview_model_map.create_scenario → set_component_attribute with
its scenario_id → solve_via_desktop_app({scenario_ids: [id], wait: true})
→ compare_scenarios(["base", id], metric).cloud_solve_submit({wait: true}) →
cloud_solve_fetch_result → the results tools on the fetched file.wait_for_solve blocks (with progress notifications)
until the jobs finish; list_jobs shows the app queue and cloud jobs
together.list_validation_rules is an index until filtered by
component_type (and group_name); the convexity://attributes/{TYPE}
resources serve the same rules.playbook validate → save → run_local with the app
open (free); then sign → run({machine_size: "xs", wait: true}) for the
cloud and publish_link for a shareable page.mcp/bench)Each prompt is also a scenario: a fixture, a reference solution (the tool calls a competent assistant makes) and a checker that reads the resulting model file directly, never through the tools under test.
npm run mcp:bench — the reference runner. Builds the bundle, runs every
file-tier scenario's reference solution against it and checks the outcome;
mcp/bench/bench.test.ts does the same in CI and also asserts that doing
nothing fails each checker. This is the regression gate for the tool
surface and its cost floor (calls, bytes, seconds).npm run mcp:bench:llm -- --model claude-opus-5 --reps 3 — the model
runner. Needs ANTHROPIC_API_KEY and spends money: a plain Messages API
tool-use loop drives the server from the prompt, with the server's own
instructions as the only system prompt, and records pass/fail per check,
calls against the reference count, tokens from the API's usage block
(--price in:out in USD per million tokens turns them into cost), wall
time and the full transcript under mcp/bench/runs/. --effort,
--scenario, --max-steps and --out narrow a run.npm run mcp:bench:report -- <run dir> --baseline <run dir> — a markdown
table per scenario and model with deltas against an earlier run, flagging
deltas smaller than the rep-to-rep spread.node mcp/dist/bench.cjs docs <run dir> — one MDX walkthrough per scenario
from a reference run, for the user docs.--tier web drives a browser tab of the web build instead: Playwright
Chromium on dist-web (served with vite preview when nothing answers
BENCH_WEB_URL, default http://localhost:4173; build it with
VITE_API_URL=<the API the bench uses> npm run build:web), signed in with
E2E_EMAIL / E2E_PASSWORD, Assistant control seeded on, and an API key
minted for that user for the run (revoked at the end). Web solves are cloud
solves, so BENCH_ACCOUNT=1 gates it. The app-tier scenarios have -web
variants; checkers read the backend's snapshot of the tab's model.
--tier app runs the scenarios that need the desktop app (--app dev
launches Electron from electron/dist through Playwright and signs in with
SCREENSHOT_EMAIL / SCREENSHOT_PASSWORD; --app installed uses the app
already running). Never run the dev app while the installed one is open —
they share the same user data. The playbook templates are write-playbook
(app tier: author, validate, save, run locally; free), publish-playbook and
cloud-playbook-run. The last two are --tier account: the app plus the real
API with the key stored on this machine (staging by default, BENCH_API_URL
overrides), gated by BENCH_ACCOUNT=1 because a cloud run is billed (the xs
minimum) and a publish leaves a link on the account.
The server is free to install and read models indefinitely. Write access
is account-gated: on startup (once per process, after the MCP handshake)
the server checks in with api.bayesian.energy/mcp/checkin using the
resolved API key. The check-in sends the server version, platform, arch,
Node version, and the MCP client's name/version — nothing about your models
or their contents. Verdicts are cached in ~/.convexity-mcp/state.json:
convexity-mcp login.lsof on macOS/Linux, a rename
probe on Windows) and opened read-only. On Linux hosts without lsof the
server warns once and cannot detect this — close the model in the
Convexity app before editing it there.FAQs
MCP server for Convexity energy models — connect Claude, ChatGPT, or any MCP client to a local Convexity model .db.
We found that convexity-mcp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.