New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

convexity-mcp

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

convexity-mcp

MCP server for Convexity energy models — connect Claude, ChatGPT, or any MCP client to a local Convexity model .db.

latest
Source
npmnpm
Version
0.6.0
Version published
Maintainers
1
Created
Source

Convexity MCP server (prototype)

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:

  • Live-UI tools (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.)
  • Exec/import (run_python, run_shell_command, sidecar import pipeline) — sandbox concerns / need the desktop sidecar.
  • The in-app agent's data tools (renewables, docs search) — they go through the app's Firebase session. Cloud solve, Modelverse and playbooks have MCP-native tools below.

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.
  • The in-app agent's data tools (get_capacity_factors, get_historical_demand, global outlook, reference costs, search_docs), authenticating with the API key (see Data tools).
  • Batch toolscreate_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.
  • Server instructions teach clients the working discipline (orient cheaply, batch, never open the file with local apps, chart/map instead).
  • Every write is followed by PRAGMA wal_checkpoint(TRUNCATE) so the bare .db file on disk is always complete.

Safety rails (Day-1 hardening)

  • Backup before first write: the first mutation per opened file snapshots <file>.bak-<timestamp> beside it; the newest 3 are kept.
  • Schema guard: models with a schema newer than the server's known line (3.5) open READ-ONLY with an explanatory message — forward-editing an unknown schema could corrupt it. Older schemas open normally (heal-on-open bridges them).
  • Concurrency guard: a file open in another application (detected via 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.

HTTP mode (claude.ai remote connector via a tunnel)

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.

Install (users)

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.

Build & pack (developers, this repo)

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.

Claude Desktop / Claude Code config

{
  "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"]
    }
  }
}

Flags

  • --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).

Signing in

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.

Cloud solve

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.

Driving the Convexity app

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.
  • Live editing. While the app is running, every command goes through the app's own command dispatch instead of this process's SQLite handle, so the model the user is looking at updates as the assistant edits — no reopen — and a model the app has open is no longer read-only here. The first edit still snapshots a <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.

Driving the Convexity web app

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:

  • with no app running, 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; or
  • enter the six-character code app_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:

  • Models live in the tab: 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.
  • Solves are cloud solves: 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.
  • The MCP-only SQL reads (the map preview, playbook links) snapshot the tab's bytes first; the first edit's backup lands under ~/.convexity-mcp/web.

Solving through the Convexity app

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.
  • Older app versions never write the file; the tools then say the app isn't running and point at cloud_solve_submit.

Modelverse and playbooks

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 validatesave (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) → signrun (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.

Data tools

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.

Batching

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.

Toolsets

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).

Positions

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.

Worked examples (prompts)

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.

Recipes

The server instructions carry the tool order that works, so an agent does not have to discover it:

  • Build and solve locallycreate_modelcreate_components_batch (buses first, with coordinates) → set_timeseries_batchopen_in_appsolve_via_desktop_app({wait: true})get_solve_results / get_summary_stats / get_pricespreview_model_map.
  • Compare a scenariocreate_scenarioset_component_attribute with its scenario_idsolve_via_desktop_app({scenario_ids: [id], wait: true})compare_scenarios(["base", id], metric).
  • Solve in the cloudcloud_solve_submit({wait: true})cloud_solve_fetch_result → the results tools on the fetched file.
  • Long solveswait_for_solve blocks (with progress notifications) until the jobs finish; list_jobs shows the app queue and cloud jobs together.
  • Vocabularylist_validation_rules is an index until filtered by component_type (and group_name); the convexity://attributes/{TYPE} resources serve the same rules.
  • Playbooksplaybook validatesaverun_local with the app open (free); then signrun({machine_size: "xs", wait: true}) for the cloud and publish_link for a shareable page.

Benchmarks (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.

Entitlement & telemetry

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:

  • Not signed in: writes work for a 14-day grace window from first launch, then lock with a message pointing at convexity-mcp login.
  • Signed in: each successful check-in refreshes the window, so offline stretches or API outages up to 14 days never lock you out.
  • Access revoked: the server degrades to read-only with the message the API returns — it never hard-fails.

Known limitations

  • Concurrent access with the app: SQLite WAL allows one writer; a model open in another application is detected (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.
  • Local solve needs the Python sidecar, which the MCP does not bundle — solve in the cloud, or through the running Convexity app (both above).
  • HTTP mode is designed for a personal tunnel, not multi-tenant hosting: one token, one shared model state per process.

Keywords

mcp

FAQs

Package last updated on 08 Sep 2026

Related posts