sysml-mcp serves OpenSysML to coding agents over the
Model Context Protocol: the agent's host (OpenCode, Claude
Code, Cursor, Codex, VS Code, Gemini CLI, Devin, ...) starts the binary in the project directory
and calls what it offers as tools — the language server's diagnostics, navigation, editing and
rendering over the documents under the workspace root; the runtime's one-shot parsing,
evaluation, instantiation, execution, verification, querying and conversion; and named REPL
sessions whose declarations, instances and runs persist across calls. The protocol is spoken over
stdin and stdout, so the host starts the server itself; nothing is typed at it, and it has no
port — unless -http names one, when a single long-lived server serves every agent that connects
over the Streamable HTTP transport (Transports).
The documents under the root (-root, the current directory by default, which a host sets to
the project) are managed as one workspace, so a declaration in one resolves against the others,
and the standard library is the copy embedded in the binary unless OPENSYSML_LIBRARY_PATH
names another — the same workspace sysml-lsp serves an editor.
The quickest way is npm: npx -y @openmbee/sysml-mcp installs the server on first use, with
nothing preinstalled but Node 20.11 or later, which is how the agent hosts' own documentation
configures a server. @openmbee/sysml-mcp
has no dependencies; its sysml-mcp command picks the platform package
@openmbee/sysml-mcp-<os>-<cpu> (linux/darwin × x64/arm64, win32 x64) for the machine, at
exactly its own version, and runs the release binary that package carries — the same bytes as
the release archive, verified against the release's .sha256 sidecars when the package was
built — with the arguments and stdio it was given. Its version is the OpenSysML release:
npx -y @openmbee/sysml-mcp@0.10.1 runs the v0.10.1 server. The same package is also
published as @openmbee/opensysml-mcp,
the name the other OpenSysML npm packages share (@openmbee/opensysml, @openmbee/opensysml-wasm):
npx -y @openmbee/opensysml-mcp runs the same release binary from the same platform packages,
and its opensysml-mcp command is a second name for sysml-mcp.
claude mcp add --transport stdio sysml -- npx -y @openmbee/sysml-mcp -root .
codex mcp add sysml -- npx -y @openmbee/sysml-mcp -root .Every release also publishes sysml-mcp-<os>-<arch>.tar.gz (.zip on Windows) for linux/amd64,
linux/arm64, darwin/amd64, darwin/arm64 and windows/amd64: the binary under its plain name, the
LICENSE and, on Unix, its manual page under share/man/man1/. It is its own archive, not part
of the opensysml-<os>-<arch> bundle, so:
# the install script, which verifies the archive against the release's SHA256SUMS.txt
curl -fsSL https://opensysml.org/install.sh | sh -s -- --tools sysml,sysml-lsp,sysml-mcp
# or by hand
tar xzf sysml-mcp-linux-amd64.tar.gz && sudo install sysml-mcp /usr/local/bin/
# or from source
go install github.com/Open-MBEE/OpenSysML/cmd/sysml-mcp@latestThe install scripts' default tool list stays sysml,sysml-lsp, so a pinned older release
still installs; sysml-mcp is installed when it is named (--tools sysml,sysml-lsp,sysml-mcp)
or with --tools all, which installs every tool the chosen release published.
irm https://opensysml.org/install.ps1 | iex with -Tools sysml,sysml-lsp,sysml-mcp does the
same on Windows, the Homebrew formula and the Scoop manifest install it beside sysml and
sysml-lsp by default, and the Windows MSI has it as the MCP server feature. A checkout builds it with
make build-mcp (into bin/sysml-mcp) or as part of make build. sysml-mcp -version reports
the build.
Each release is also listed in the official MCP Registry
as org.opensysml/sysml-mcp, pointing at the npm package @openmbee/sysml-mcp, so a host or
directory that reads the registry offers it without any of the above. Installing from there
configures npx -y @openmbee/sysml-mcp, which downloads the release binary for the platform
and runs it; the -root argument the listing declares is the workspace folder, the current
directory by default.
The GitHub MCP Registry, which VS Code's MCP: Browse MCP Servers
(the Extensions view's MCP Servers section) and Copilot read, is a curated selection of the
official registry, not a mirror: GitHub adds a server by hand on request and syncs its later
versions from the official registry on its own. Until org.opensysml/sysml-mcp is onboarded
there, VS Code is configured as below.
Each host reads a configuration file of its own, and -print-config <host> writes the exact
block for it to stdout:
sysml-mcp -print-config # list the hosts
sysml-mcp -print-config opencode # the block for one
sysml-mcp -print-config codex >> ~/.codex/config.tomlThe block names the server sysml, so its tools appear to the agent under that prefix
(sysml/diagnostics or mcp__sysml__diagnostics, as the host spells it), and names the binary
by its resolved absolute path (os.Executable, symlinks followed), so the host finds it wherever
it was installed and however it starts the process. To name a command instead — sysml-mcp when
the binary is on PATH, or a wrapper — give -print-config-command; to have the host run the
server from npm instead, npx -y @openmbee/sysml-mcp, give -print-config-npx, which writes
npx as the command and -y @openmbee/sysml-mcp as the first arguments:
sysml-mcp -print-config cursor -print-config-command sysml-mcp
sysml-mcp -print-config vscode -print-config-npxThe blocks below were written with -print-config-command /usr/local/bin/sysml-mcp; with
-print-config-npx the command is "npx" and the args array begins
"-y", "@openmbee/sysml-mcp" (OpenCode's command array becomes
["npx", "-y", "@openmbee/sysml-mcp"], and Codex's args = ["-y", "@openmbee/sysml-mcp"]). A
host that should serve fewer tools or another folder gets them as arguments: add
"-tools", "lsp,runtime" or "-root", "models" to the args array (the command array in
OpenCode's case). Hosts start the server with the project as its working directory, which is
what -root defaults to.
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"sysml": {
"type": "local",
"command": [
"/usr/local/bin/sysml-mcp"
],
"enabled": true
}
}
}OpenCode reads opencode.json in the project and ~/.config/opencode/opencode.json for every
project; "type": "local" is a server OpenCode starts itself. The mcp key sits beside the
lsp key that declares sysml-lsp (editors and agents), and
this repository's own opencode.json declares both, with sysml-mcp
expected on PATH.
{
"mcpServers": {
"sysml": {
"type": "stdio",
"command": "/usr/local/bin/sysml-mcp",
"args": []
}
}
}.mcp.json in the project root is shared with the project (Claude Code asks before using it);
claude mcp add --scope user sysml -- /usr/local/bin/sysml-mcp writes the same declaration
into the user's settings instead.
{
"mcpServers": {
"sysml": {
"command": "/usr/local/bin/sysml-mcp",
"args": []
}
}
}Project-wide in .cursor/mcp.json, or for every project in ~/.cursor/mcp.json.
[mcp_servers.sysml]
command = "/usr/local/bin/sysml-mcp"
args = []A table of Codex's config.toml; >> appends it. The Codex CLI and the Codex IDE extension read
the same file.
{
"servers": {
"sysml": {
"type": "stdio",
"command": "/usr/local/bin/sysml-mcp",
"args": []
}
}
}GitHub Copilot's agent mode reads .vscode/mcp.json in the workspace, or the mcp section of the
user settings for every workspace (MCP: Add Server in the command palette writes either).
{
"mcpServers": {
"sysml": {
"command": "/usr/local/bin/sysml-mcp",
"args": []
}
}
}.gemini/settings.json in the project or ~/.gemini/settings.json for the user; the
mcpServers key is merged into the file's other settings.
{
"mcpServers": {
"sysml": {
"command": "/usr/local/bin/sysml-mcp",
"args": []
}
}
}Devin runs custom MCP servers from its organization settings (Settings → MCP Marketplace → Add
custom): paste the mcpServers block, with the command resolving on the machine Devin runs on
— a path under the repository checkout after make build-mcp, or sysml-mcp once the
environment's setup installs it on PATH.
The server offers three groups, chosen with -tools lsp,runtime,session (every group by
default): lsp is the language server's workspace over the documents under the root, runtime
the one-shot service also served by sysml-grpc — one tool per RPC,
with the RPC's request as the arguments and its response as the structured result (the
conformance suite runs through these tools as protocol mcp,
so they answer the scenarios the service does) — and
session persistent REPL sessions, one tool per % command. Serving fewer groups keeps the
catalogue small for an agent that needs only one of them. The catalogue below is the server's
own tools/list, with each tool's description cut to its first sentence; the tool's full
description, read by the agent, says what every argument does.
No two groups share a tool name: the lsp group's workspace_diagnostics, render_view and
render_document work on a document of the workspace, the runtime group's diagnostics,
render_model_view and render_model_document on a model given as paths, inline sources or
a modelHash. Every server also serves tool_catalog, which
answers this catalogue — each tool's group, the scope a caller needs, the sysml-grpc
RPC or sysml flag it stands for and a one-line summary — narrowed to the groups served, so an
agent can discover the surface without this page.
| Tool | Does | Writes |
|---|---|---|
apply_model_edit |
Edit a model by operations on its elements rather than by text: setValue, rename, addMember, addConnection, addSequence (action-body items), delete, move, and the diagram-layout annotations setLayout, setRoute, setCanvas, setStyle, setNote, setPicture. | yes |
close_document |
Release a document held open by open_document: the server goes back to the file on disk (or forgets the document when there is no file). | yes |
code_actions |
List the quick fixes and refactorings available for a range of a document, each with the edits it would make. | no |
completion |
Propose completions at a position: keywords, names in scope and members of the type being accessed. | no |
definition |
Find where the name at a position is declared. | no |
document_symbols |
Outline a document: the elements it declares, nested as they are owned, with their kinds and ranges. | no |
documents |
List the document definitions (doc def) declared across the workspace, which render_document renders to Markdown. | no |
format |
Format a SysML v2 / KerML document, or only the lines of a range, in the canonical style. | yes |
hover |
Explain the element at a position: its kind, qualified name, type, value and documentation, as Markdown. | no |
import_diagram |
Import a draw.io file render_view wrote with form drawio and a user edited: moves, routes, styles, the page, note text and picture sources as DiagramLayout annotations in the view's body, a retyped name as a rename; the rest is reported; a dry run until apply. | yes |
inlay_hints |
List the inferred types and values the editor would show inline for the features of a document or range. | no |
open_document |
Hold a document open with the given text, as an editor buffer the server analyzes instead of the file on disk, so text can be checked (diagnostics, hover, render) before it is written. | yes |
references |
Find every place in the workspace that refers to the element at a position. | no |
rename |
Rename the element declared or referenced at a position, everywhere in the workspace. | yes |
render_document |
Render a document definition (doc def) to Markdown, its queries evaluated over the workspace and its diagrams embedded. | no |
render_view |
Render a diagram or table of a document: a declared view or a pseudo-view, as Mermaid, Graphviz dot, PlantUML, D2, text, Markdown, CSV or TSV. | no |
signature_help |
Describe the invocation enclosing a position: the callable's parameters and which one the position is at. | no |
update_document |
Replace the text of a document held open by open_document and re-analyze it. | yes |
views |
List the views a document declares and the pseudo-views (#tree, #interconnection, ...) render_view can draw of it. | no |
workspace_diagnostics |
Analyze a SysML v2 / KerML document, or the whole workspace, and list the errors, warnings and hints: syntax, unresolved names, type and constraint violations. | no |
workspace_info |
Describe the SysML v2 workspace the server has indexed: its root folder and the model documents found under it. | no |
workspace_symbols |
Search the workspace's declared elements by name substring. | no |
| Tool | Does | Writes |
|---|---|---|
apply_edits |
Apply source-preserving edits to a parsed model and return the edited notation (the ApplyEdits RPC): set a value, rename with every reference, add a member, import, documentation, comment, connection, transition, satisfy or metadata, delete, move. | no |
check_behavior |
Search every schedule of an action or state machine (the CheckBehavior RPC, the REPL's %check-property/%check-diverge settings about %action/%state): a property false at a stable state, a deadlock, a failure or a feature whose final value the schedule decides is witnessed and replayed. | no |
compare_results |
Run every configuration a SysML v1 tool's results sidecar indexes against the migrated model (the CompareResults RPC, the CLI's -compare-results with -action, -runs, -seed, -draws, -clock-step and -observe): each on its target, for its recorded run count under its duration mode, or the runs and draws given, and table the tool's and OpenSysML's min, mean, p50, p90 and max of each observable with their relative difference. | no |
convert |
Convert a SysML v2 model between the representations OpenSysML writes — textual notation, RDF Turtle, the API's JSON element form — (the Convert RPC, the CLI's -convert). | no |
diagnostics |
Report every diagnostic of a parsed model (the GetDiagnostics RPC), as sysml -validate does: each with its severity, code, message and position. |
no |
element_properties |
Read the metamodel properties of one element of a parsed model by their KerML/SysML specification names — inheritedFeature, connectorEnd, relatedFeature, directedFeature, isComposite, a feature's value… — as the model's API element form spells them (the GetElementProperties RPC). The element is named by qualified name, by the positional id of an unnamed member or by the id an earlier answer's element value carried, which walks the model and into the standard library; a property the metaclass does not declare, or OpenSysML does not derive for the element, answers unsupported with the reason. |
no |
evaluate |
Evaluate a SysML expression against a parsed model (the Evaluate RPC, the REPL's %eval): arithmetic, feature reads, quantities with units, collection operators. | no |
evaluate_calc |
Invoke a calculation (the EvaluateCalc RPC, the REPL's %calc): a calc definition with positional arguments answers its result; a calc usage named with no arguments binds its inputs from its own members and answers every output feature in outputs. |
no |
execute_action |
Run an action definition or usage to completion and report its output parameters (the ExecuteAction RPC, the REPL's %run). | no |
execute_state |
Run a state machine over a sequence of events and report the states visited, the final context and the simulation clock (the ExecuteState RPC, the REPL's %run on a state). | no |
explain_diagnostic |
Explain a diagnostic code (as the CLI's -explain does): what it means, how to resolve it and the specification clause or validation constraint behind it. | no |
export_graphs |
Export the lowered graph of an action or state machine — the subject's and every behavior it performs — as the canonical graphs: JSON external analysis engines read (the ExportGraphs RPC). | no |
import_data |
Set the feature values a CSV, TSV, JSON or JSON Lines file assigns, one row per element (the ImportData RPC, the CLI's -import with -import-as, -import-map, -import-format and -import-dry-run): a feature the element declares has its value set, one it inherits is redefined in the element's body with the value, as one edit per file; a value the model refuses leaves every row unapplied and answers error. |
no |
instantiate |
Build an object of a part definition or usage and report its feature values (the Instantiate RPC, the REPL's %instantiate): every attribute's value or default, nested parts as instance ids, with every reachable instance in instances. |
yes |
list_engines |
List the analysis engines this build registers (the ListEngines RPC, as the CLI's -engines does): each with the questions it answers (evaluate, outcomes, holds, sensitive, satisfiable, sweep, compute), the strongest evidence it can produce, the bounds it takes, and whether it can run here. | no |
migrate |
Migrate a SysML v1 model — UML XMI with the SysML profile, an Eclipse UML2 .uml file or a Cameo/MagicDraw .mdzip archive — to SysML v2 in one of the representations convert writes (the Migrate RPC, the CLI's -migrate). | no |
parse |
Parse SysML v2 / KerML documents as one model and return its modelHash, the root namespace of each document and every diagnostic (the ParseSources RPC). | no |
query |
Select elements of a parsed model with a SysML v2 API & Services Query — scope, select, where, orderBy — or OSLC query text (the Query RPC), run as a document-query plan over the model's elements, or over the objects instantiate created when @type names an element (rdf:type=elmt:<id>); an object answers its path too. |
no |
record_run |
Run an analysis case — once, over the rows of a sweep (ranges), or as a Monte Carlo sample (runs) — and record the runs into the model as AnalysisRecords declarations (the RecordRun RPC, the CLI's -record-run/-record-into, the REPL's %record). |
no |
render_model_document |
Render a named document of a model given by modelHash, paths or sources (the workspace group's render_document renders by name over the root) — a part def specializing DocumentQueries::Document — to Markdown or HTML (the RenderDocument RPC, the CLI's -render-document), byte for byte what the CLI writes. | no |
render_model_view |
Render a declared view or a targeted pseudo-view of a model given by modelHash, paths or sources as diagram data (the RenderView RPC; the workspace group's render_view renders by name over the root): nodes flattened parents first, edges with their ports, table columns and rows, canvas, notes and notices. | no |
run_analysis |
Run an analysis case (the RunAnalysis RPC, the REPL's %analysis): its subject and inputs bound from the arguments and its own declarations, its body run, every declared output answered with the verdict of its objective and of each assertion in its body; a verification case also answers its own verdict. | no |
run_document_query |
Run a named document query with parameter bindings, or a query expression written in place of a name (inlineQuery, planned by the same compiler in the model's context; needs the inline_document_query capability), and answer its typed rows (the RunDocumentQuery RPC, the REPL's %run-query). |
no |
run_runs |
Run an action, or a Simulation::MonteCarlo analysis case, a number of times (the RunRuns RPC, the CLI's -runs/-observe, the REPL's %runs): every run is an ordinary run in a context of its own, drawing its modeled randomness from a seed derived from seed. |
no |
run_sweep |
Run an analysis case or calc once per row of a parameter sweep (the RunSweep RPC, the CLI's -sweep): each row an ordinary run with the swept parameters bound to that row's values, stepped through the ranges' cartesian product or drawn with samples and seed. | no |
self_check |
Apply the SysMLValidation constraints, and those of any rule packages named, reflectively to every element of a model (the SelfCheck RPC, the CLI's -self-check and -self-check-package): each element is handed to every constraint whose input its metaclass conforms to. | no |
server_info |
Report the OpenSysML runtime's version and the named capabilities this build supports. | no |
symbol |
Describe one element of a parsed model by qualified name (the GetSymbol RPC): its kind, metadata, children, members (own, then inherited with the type each comes from; standard-library ones withheld unless asked for), attributes with their declared values, static type facts, multiplicity and specializations. | no |
validate_instance |
Validate an object of a part as a whole (the ValidateInstance RPC, the REPL's %validate): every assertion about an object of symbolId and about the objects it holds — asserted constraints of their types, the requirements they carry, the satisfactions they are the subject of — each answered on the concrete object, with a summary verdict about the object as a whole. |
no |
verify_constraint |
Ask whether a constraint holds (the VerifyConstraint RPC, the REPL's %constraint): against its declared values, or against an object of subjectSymbolId built for the call. | no |
verify_requirement |
Ask whether a requirement is satisfied (the VerifyRequirement RPC, the REPL's %requirement): its require constraints evaluated against declared values or against an object of subjectSymbolId. | no |
verify_satisfaction |
Evaluate the satisfy assertions a model states (the VerifySatisfaction RPC, the REPL's %satisfy): each against an object of its subject built for the call, answering one Verdict per assertion in declaration order. | no |
| Tool | Does | Writes |
|---|---|---|
advance |
Advance the debuggers' simulation clock (%advance), running every state event, action token, change-condition poll and do behavior that comes due, in due order, and report what ran and where the debuggers stand. | yes |
check |
Ask an SMT solver whether a constraint, requirement or satisfaction can be satisfied at all (%check, experimental), with a satisfying assignment when it can. | no |
configure |
Ask an SMT solver which variants an element's conditions permit (%configure, experimental): a consistent selection, the check of the selections given, or every consistent selection up to a limit. A part or definition is configured over every assertion about its objects, those its nested parts' own objects carry included (a variant's applying only where its variation is selected, and a variation point a variant alone declares enumerated under that variant only); a nested part the query cannot read is named with the reason. | no |
debug_breakpoints |
Set breakpoints at nodes of the debugged action (%break), optionally clearing the earlier ones, and report the breakpoints it then has; with nothing to set, lists them. | yes |
debug_continue |
Run the debugged action to completion, or to the first breakpoint a token reaches (%continue), and report its results. | yes |
debug_current |
Report where the debugging session stands without changing it: the action's execution state, tokens, breakpoints and feature values (%tokens), or the machine's current state, clock, event queue, signals in flight and state data (%current, %events). | no |
debug_start |
Start a debugging session on an action (%action) or a state machine (%state), performed by an object when one is named. | yes |
debug_step |
Advance the debugged action one token step, or the debugged state machine one step (%step): one event dispatched, one change event, one round of do actions. | yes |
debug_stop |
End the debugging session (%stop), letting go of its run; objects the session holds stay. | yes |
explain |
Ask an SMT solver which conditions of an unsatisfiable element conflict (%explain, experimental): a minimal set of conditions, each with the element declaring it and its location. | no |
features |
Show an object's feature values and what its behaviors are doing (%features): the values its attributes hold, the objects its parts and ports hold, its connectors, and the state of each machine it exhibits and action it performs. | no |
instances |
List the objects a session holds (%instances): each with its id, type and lifetime, the ones a later instantiation displaced included. | no |
optimize |
Ask an SMT solver for the best values an analysis case's objectives admit (%optimize, experimental), each objective improved as its trade-study definition says, within the case's conditions. | no |
render_run |
Render the run a session recorded as a state timeline or a message sequence (%render-run), in a text or diagram form. | no |
repl_command |
Run one sysml prompt command (a % line) in a session and return what it printed. | yes |
runs |
Run an action count times, each run's modeled randomness seeded on its own, and return the table of the observables with each one's distribution (%runs); or run a Simulation::MonteCarlo analysis case count times on fresh subjects and conclude it. | yes |
send |
Send a signal to an object through the runtime's message bus (%send), for the state machine it exhibits or the action parked at a matching accept to take; by default to the object whose behavior is being debugged. | yes |
session_clear |
Reset a session, as %clear does: every declaration, object and debugging session goes; the session stays open and empty. | yes |
session_close |
Close a REPL session, discarding its model, instances and any run in progress. | yes |
session_eval |
Evaluate an expression in a session, as %eval does: a literal, a declared feature or qualified name, an enumeration literal, a unit, or a compound expression over the session's declarations. | no |
session_list |
List the open REPL sessions. | no |
session_load |
Load model files into a session, as %load does: files, directories or globs, resolved against the workspace root and refused outside it. | yes |
session_open |
Open a named REPL session: an empty model that declarations, instances and runs accumulate into across calls. With root, a directory under the workspace root, the session loads a model file under it on demand when a submission imports or qualifies a root namespace that neither the session nor the library declares, as %load would load it. |
yes |
session_print |
Print a session's model as SysML notation, or one named element with its body, as %print does. | no |
session_run |
Run a behavior in a session to completion and report what it produced: an action (%action then %continue), a state machine (%state, then %advance when a duration is given), a calc (%calc) or an analysis case (%analysis). Its arguments are expressions evaluated together in one run, as the REPL evaluates an argument list, so a seeded draw in one continues the stream of the draw before it. |
yes |
session_save |
Write a session's model to a file under the workspace root, as %save does: .sysml notation with comments preserved, or .ttl/.json RDF (experimental). | yes |
session_submit |
Submit SysML declarations to a session, as typing them at the prompt does: the text joins the model, the whole model is re-analyzed, and what it declared and the diagnostics about it come back. | yes |
solve |
Ask an SMT solver for values satisfying an element's conditions (%solve, experimental), keeping the values the model already fixes; reports the conflict when the fixed values admit none. | no |
sweep |
Run a calc or analysis case once per row of the ranges given (%sweep), or over rows drawn from them (%samples), and return the table: each row's inputs, outputs, verdicts and timing. | yes |
- Positions in arguments and results are 1-based
lineandcolumn(a column counts characters, not UTF-16 code units; the server converts). - Paths are absolute or relative to the workspace root (
-root), and are resolved against it; a tool that takespathsaccepts files, directories and globs. A path must stay under the root: one that leaves it, by its form (../secret.sysml, an absolute path elsewhere) or through a symlink, is refused, and writes are performed through the resolved root itself, so a link repointed after the check cannot carry one outside it. Thelsptools can also work on text held open withopen_documentinstead of a file on disk. - Round-tripping a diagram is
render_viewwithform: "drawio", an edit in draw.io, thenimport_diagramwith the edited file (fileon the host orcontentas text): the tool renders the view again, diffs the file against that export and answers theDiagramLayoutannotations the differences amount to —Layout,Route,Style,Canvas,Note(a moved or retyped note),Picture(a moved picture or one pointed at another image file) — and, for a cell whose text was retyped with only its name changed, therenameof that element and every reference to it, as the text editsapply_model_editwould make, per document. Call it first withoutapplyto read the plan and what it reports rather than imports (cells naming no element, elements the rendering lacks, deleted cells, a label edited beyond its name, a name that is not one or that an existing member holds, an emptied note, a picture given embedded data, a URL or a file outside the view's directory, and changes no annotation carries yet, such as a re-routed typing edge), then with"apply": trueto write the files. - Writing happens only on request: a tool marked as writing (
format,rename,apply_model_edit,session_save, ...) returns the edit or the file it would write and touches the disk only with"apply": true. The session tools that write change the session's state (declarations, instances, a run), not files, unless they say so. - Results are structured JSON (
structuredContent, the same shape the CLI, the REPL or the RPC answers) and a readable text with the same content; a tool-level failure is anisErrorresult with the message, not a protocol error. - Budgets. Long-running work honours the runtime's budgets —
OPENSYSML_MAX_STEPS,OPENSYSML_MAX_ACTION_STEPS,OPENSYSML_MAX_EVENTS,OPENSYSML_MAX_DO_STEPSandOPENSYSML_MAX_ELEMENTS(environment variables) — set in the environment the host starts the server with; a run that exhausts one is reported as a runaway, with the budget named.
Every tool requires one scope — an OAuth 2.0 scope token (RFC 6749 § 3.3) — named by
tool_catalog and in the Tools section of the manual page. A tools/call by a principal whose
credential does not grant the tool's scope is refused with a JSON-RPC error (code -32030,
insufficient_scope) whose message and data name the tool and the scope, so an agent can ask for
the scope rather than guess; tools/list is never narrowed, so the agent still sees what the server
has. Over stdin and stdout, and over -http without a token, the one caller is the local principal
and holds every scope, so a host that starts the server itself loses nothing. The bearer token
named by -token-file, -token or OPENSYSML_MCP_TOKEN holds every scope as well; a credential an
authorization server issues carries the scopes it was granted.
| Scope | Grants | Tools |
|---|---|---|
sysml:read |
Ask about the workspace and the model: queries, diagnostics, hover, conversions and edits answered as text; nothing another caller sees changes | apply_edits, code_actions, completion, convert, debug_current, definition, diagnostics, document_symbols, documents, element_properties, evaluate, evaluate_calc, explain_diagnostic, export_graphs, features, hover, import_data, inlay_hints, instances, list_engines, migrate, parse, query, references, render_document, render_model_document, render_model_view, render_run, render_view, repl_command, run_document_query, self_check, server_info, session_print, signature_help, symbol, tool_catalog, verify_constraint, verify_requirement, verify_satisfaction, views, workspace_diagnostics, workspace_info, workspace_symbols |
sysml:write |
Write the workspace: files replaced with apply, documents held open or released, prompt commands that may save |
apply_model_edit, close_document, format, import_diagram, open_document, rename, session_save, update_document |
sysml:execute |
Execute the model: actions, state machines, analyses, solving, debugging and REPL evaluation in a session | advance, check, check_behavior, compare_results, configure, debug_breakpoints, debug_continue, debug_start, debug_step, debug_stop, execute_action, execute_state, explain, instance_paths, instantiate, optimize, record_run, run_analysis, run_runs, run_sweep, runs, send, session_eval, session_run, solve, sweep, validate_instance |
sysml:session |
Open, load, list, clear and close your own REPL sessions; a session name is private to the principal that opened it | session_clear, session_close, session_list, session_load, session_open, session_submit |
sysml:admin reaches across principals and is no tool's scope: it widens session_list to every |
||
principal's sessions, each with its owner, and lets session_close close another principal's |
||
session by owner. A write-scoped tool that writes only with "apply": true requires the scope |
||
even without it, as the scope says what the tool can do. repl_command is admitted with |
||
sysml:read and then requires, for the % command it is given, the scope of the typed tool doing |
||
the same — sysml:read to show or list (%list, %print, %tokens, …), sysml:session to load, |
||
clear or set the session up (%load, %clear, %seed, the %check-* settings, …), sysml:write |
||
to %save, %publish or %check-witness (which names where checks write, under the root), sysml:execute to instantiate, evaluate, run, solve or debug (%eval, |
||
%action, %solve, …) — refusing a command the credential lacks the scope for before the session |
||
| is touched; the refusal names the command. |
Every tools/call is recorded on the log at level info (-log-level info), one line per call
with the principal (its subject and source: local, static-token, or the kind of credential),
the remote address, the tool, its scope, the outcome (ok, refused, error) and the duration —
never the credential, and never the arguments.
Without -http the protocol is spoken over stdin and stdout: the host starts the server,
which serves that one client and exits when the stream closes. Nothing but protocol messages is
written to stdout; logs go to stderr.
With -http ADDR the server is started once and speaks the Streamable HTTP transport at the
path /mcp of that address to every client that connects — remote agents, or several agents over
one project — until it is told to stop with SIGINT or SIGTERM. Stopping closes the clients'
sessions, so their event streams end, and gives the connections up to -shutdown-timeout (30 seconds) to drain. The
clients share one workspace and one runtime cache. REPL sessions belong to the principal that
opened them — the identity of the credential, source:subject, or the one local principal when the
server authenticates nobody — so a session_* name one client opens is the session every other
client of the same principal sees under that name, session_list lists that principal's sessions
and session_close closes one of them; another principal's default or work is a different
session, and neither sees nor closes the other's. Only a credential with the sysml:admin
scope lists every principal's sessions, each with its owner, and closes another's by
owner. A session's calls are served in turn, so two agents of one principal using one session
interleave rather than collide; agents that want sessions of their own give them different names.
sysml-mcp -http 8765 # http://localhost:8765/mcp, this machine only
sysml-mcp -http 0.0.0.0:8765 -token-file mcp.token -tls-cert server.crt -tls-key server.key
sysml-mcp -http 0.0.0.0:8765 -token-file mcp.token -allow-origin https://agents.example.org
sysml-mcp -http 0.0.0.0:8765 -api-keys keys.jsonl -tls-cert server.crt -tls-key server.key # a key per callerThe stance is the one sysml-grpc takes for TLS and origins, with the
authentication the Model Context Protocol asks of a server on a network:
- Loopback needs no token. A port alone, or an address with no host, binds loopback, which only this machine reaches, and is served as it is: every local client is the local principal, holds every scope and may read and edit the workspace, as the stdio transport lets the host that starts the server do.
- Any other address needs a bearer token. The protocol's authorization specification makes
an HTTP server an OAuth 2.1 resource server; this server is one in the minimal form, with one
access token issued out of band by whoever runs it rather than by an authorization server,
since a single binary has none to delegate to. The token is named by
-token-file FILE(preferred: the file's trailing line break is not part of it, and the token stays out of the process list),-token TOKEN, or theOPENSYSML_MCP_TOKENenvironment variable when neither flag is given. Starting on an address other machines reach (0.0.0.0:8765, a host name) with no token is refused with exit status 2 and a message naming the flags — not served with a warning. A token given for a loopback address is enforced the same way, for a machine shared with others. A file of API keys (-api-keys FILE) is a credential as well, alone or beside the token: a key is tried first, by itsosk_prefix, and any other bearer token against the token. - Every request carries the token as
Authorization: Bearer TOKEN(RFC 6750 § 2.1, the only form the protocol allows; a token in the query string is never read). The comparison is constant-time. A request without it is refused with401 Unauthorizedand aWWW-Authenticate: Bearer realm="sysml-mcp", resource_metadata="…"challenge (RFC 6750 § 3, RFC 9728 § 5.1); one with a wrong token addserror="invalid_token", one with another schemeerror="invalid_request", and a malformedBearerheader is400 Bad Request. The refusal never repeats the token, and the token is logged nowhere. - A protocol session belongs to who opened it. An
Mcp-Session-Idis answered only to the principal whoseinitializecreated it,403 Forbiddento any other. Every holder of the one token is the same principal,static-token:token, so clients that share the token may still share a session. - Resource metadata. With a token, the protected resource metadata of RFC 9728 is served at
/.well-known/oauth-protected-resource/mcp— the endpoint'sresourceidentifier as the client reached it andbearer_methods_supported: ["header"], with noauthorization_serversto list — so a client following the challenge learns that the token is preconfigured rather than obtained by a flow. An authorization server that issues tokens by OAuth 2.1 (user consent, scopes, expiry) is a proxy's job in front of the server, as it is forsysml-grpc. Behind a proxy that terminates TLS or rewrites the path, a request no longer shows the URL the client used, so-public-url https://models.example.org/mcpnames it: the challenge, the metadata and the-print-configblock then carry that URL rather than the request's own scheme andHost. The proxy must forward the well-known URI as well as the endpoint: with-public-url https://models.example.org/agents/mcpthe server answers the metadata at both/.well-known/oauth-protected-resource/agents/mcpand/.well-known/oauth-protected-resource/mcp, so a proxy that forwards the path unchanged and one that strips the/agentsprefix both reach it. - The token itself is letters, digits and
-._~+/with trailing=(RFC 6750b64token), so a client can send what the server was given; generate one (openssl rand -base64 32) and keep it in a file only the server's user reads. Over a network it needs TLS, below: a token served in cleartext on an address other machines reach is logged as a warning, since the one case it is right for is a TLS-terminating proxy on the same host or private network. - TLS.
-tls-cert FILEand-tls-key FILE, both given, servehttps(TLS 1.2 or later); one without the other, or a pair that does not load, is a usage error (exit status 2) before anything is served. Cleartext is appropriate only on loopback, inside a trusted network or behind a TLS-terminating proxy. - Origins. A browser page reaches a server on this machine only through the
Originheader the browser sets, and a page on another site can be pointed at a DNS name rebound to this machine, so the server answers a request when itsOriginheader is absent (a non-browser client), names a loopback origin (localhost,127.0.0.0/8or::1, over any scheme and port) or names an origin listed in-allow-originexactly (scheme://host[:port], comma-separated, compared case-insensitively on scheme and host); any other origin is refused with 403. The wildcard origin is not accepted: name each one. Only a listed origin is granted cross-origin access — theAccess-Control-Allow-Originheader on its responses and an answer to its browser's preflight — so a page served from another port of this machine passes the check yet is still refused by its browser unless it is listed: a loopback origin is admitted so that it is not mistaken for rebinding, not so that every local page may edit the workspace. This is not authentication — it keeps browsers from being used as the way in. - Protocol version. A request whose
MCP-Protocol-Versionheader names a version the server does not speak is refused with 400, as the protocol requires; a request with no header is read as the version negotiated atinitialize. The SDK's check that a loopback server is addressed as loopback (theHostheader) stays on as well.
-print-config HOST -http ADDR writes the host's block in its URL form, so the host connects to a
server already running rather than starting one, and with the server's token (-token-file,
-token or OPENSYSML_MCP_TOKEN) the block carries the Authorization header the host sends
(Configuring a host). The token is written in clear, so such a block belongs
in a file the host reads privately — a user-level configuration, or a project file kept out of
version control — or is edited to the host's own reference to a secret where it has one.
A service with several callers — people, CI jobs, other agents — needs a credential per caller that
can be revoked alone, which one shared token is not. sysml-mcp mints and revokes API keys in a
file; the file is the server's key store.
sysml-mcp -mint-api-key -api-keys keys.jsonl -subject alice@example.org -scopes sysml:read,sysml:write -expires 720h
sysml-mcp -revoke-api-key Jf3kQ9pLmN2x -api-keys keys.jsonl- The key is
osk_<id>.<secret>: theosk_prefix tells it from any other bearer token, the id (12 characters) names its record, and the secret is 32 random bytes in base64url. It is printed once, to stdout alone, when minted; store it where a token belongs (a secret store, a CI variable, a file only its user reads). The file never holds it, so a copy of the file does not give a key away, and a key that is lost is minted again rather than recovered. - The file is one JSON object per line (JSON Lines),
{"id", "sha256", "subject", "scopes", "expires", "disabled"}: the SHA-256 of the secret in hex, the subject the key speaks for, the scopes it grants, its expiry (RFC 3339, whole seconds, absent for a key that does not expire) and whether it is disabled. Minting appends a line; the file is created with mode0600and kept so. A line that is not a record, a scope that is not an RFC 6749 scope token, or an id held twice fails the file at that line, and the file is not edited. - Rotation and revocation.
-revoke-api-key IDmarks the record disabled — the line stays, so the subject and the history are kept — and a server reading the file refuses the key from its next look at the file. To rotate, mint the subject a new key, move the caller to it, then revoke the old id. A key past itsexpiresis refused as expired without being revoked. Two edits at once wait for each other through akeys.jsonl.lockbeside the file; one left by a process that died is named for removal. - Scopes are the RFC 6749 § 3.3 scope tokens the key grants (printable ASCII without spaces,
quotes or backslashes), comma-separated at
-scopes; a key has at least one. What each scope admits is the server's to say when it reads the file.
A server given such a file (internal/frontend/guard/apikey) accepts a key as Authorization: Bearer osk_… with the record's subject, scopes and expiry as the request's principal, looking the
record up by id and comparing the secret's hash in constant time, and refuses a disabled, expired or
unknown key with the RFC 6750 § 3.1 answer; a bearer token that is not a key is left to the server's
other authenticators (its static token last). The file is read again when its content has changed
(looked at, at most once a second, on a request) and on SIGHUP; a file that no longer reads
keeps the keys already held and logs the failure once. sysml-mcp -http ADDR -api-keys FILE serves
the file this way, alone or beside -token-file/-token, and satisfies the rule that an address
other machines reach needs a credential; kill -HUP the server after editing the file by hand to
have it read at once rather than at the next request.
| Flag | Default | Meaning |
|---|---|---|
-root DIR |
. |
Workspace folder whose model documents the server indexes and edits |
-tools GROUPS |
all |
Tool groups to serve, comma-separated: lsp, runtime, session |
-strict |
off | Strict conformance from the start: notation no pinned SysML v2 production admits is an error |
-no-record-cache |
off | Parse every document and hold it loaded, reading and writing no interface record (also OPENSYSML_RECORD_CACHE=0) |
-cache-size N |
100 |
Parsed models the runtime tools hold, at least 1 |
-log-level LEVEL |
warn |
Lowest level logged to stderr: debug, info, warn or error |
-stdio |
— | Serve over stdin/stdout; the default transport, accepted for hosts that name it; not with -http |
-http ADDR |
— | Serve over Streamable HTTP at /mcp of host:port, or of loopback for a port alone; every client shares the workspace and runtime cache, and the REPL sessions of its principal; an address other machines reach needs -token-file, -token or -api-keys |
-allow-origin ORIGINS |
— | Origins (scheme://host[:port], comma-separated) granted cross-origin access; loopback origins and requests with no Origin are admitted without a grant, others are refused |
-token-file FILE |
— | File holding the bearer token every request must carry as Authorization: Bearer TOKEN; required for an address other machines reach, enforced on loopback too when given |
-token TOKEN |
— | The token itself; OPENSYSML_MCP_TOKEN is read when neither flag names one |
-public-url URL |
the request's scheme and Host |
The URL clients reach the endpoint at behind a proxy that terminates TLS or rewrites the path; named as the resource, in the bearer challenge and by the -print-config block |
-tls-cert FILE, -tls-key FILE |
— | Serve https; both or neither |
-shutdown-timeout DURATION |
30s |
How long a stopping HTTP server waits for its connections to drain after closing the sessions |
-max-body-bytes N |
4194304 |
Largest request body the HTTP transport reads at /mcp; a larger one is refused with 413 |
-api-keys FILE |
— | File of API keys, one JSON record per line holding the SHA-256 of a key with its subject, scopes, expiry and whether it is disabled; read by -http as a credential ahead of the token, re-read when it changes and on SIGHUP; appended to by -mint-api-key, edited by -revoke-api-key |
-mint-api-key |
— | Mint a key osk_<id>.<secret> for -subject NAME with -scopes a,b, expiring after -expires DUR when given; print it once to stdout, append its record to the -api-keys file and exit |
-revoke-api-key ID |
— | Mark the key of this id disabled in the -api-keys file and exit |
-print-config [HOST] |
— | Write a host's configuration block to stdout and exit; alone, list the hosts |
-print-config-command CMD |
the binary's path | The command the block names; with -http ADDR (and -tls-cert for https) the block names the server's URL instead, with the Authorization header when a token is given |
-version, -help, -man |
— | The build, this table, the manual page |
Every flag is also read from the environment as OPENSYSML_MCP_<FLAG> — the flag's name in upper
case with each dash an underscore, OPENSYSML_MCP_LOG_LEVEL for -log-level — when the command
line leaves it unset; a flag given wins over its variable, an empty variable is as good as unset,
and a value the flag would refuse is refused the same way, with exit status 2. OPENSYSML_MCP_TOKEN
is the variable of -token and OPENSYSML_MCP_TOKEN_FILE that of -token-file, each read only
when neither token flag is given, the file first. With -http the server also answers GET /health
and GET /ready without a token; Running sysml-mcp as a service
covers them, the environment, the container image and the systemd unit.
The exit status is 0 when the protocol was served to its end (the client closed the stream or the
process was told to stop), 1 when the server could not start (the -http address in use, say) or
the session ended in a protocol error, and 2 when the command line could not be acted on. man sysml-mcp has the same content
once the manual page from the archive is installed.
- Never run it in a shell.
sysml-mcpat a prompt prints nothing and waits: stdin is the protocol stream, and the first line typed at it is a protocol error. Let the host start it. To see that the binary runs, usesysml-mcp -version; to see what a host would get, drive it with a client such asnpx @modelcontextprotocol/inspector --cli /usr/local/bin/sysml-mcp --method tools/list. - Logs are on stderr. Nothing but protocol messages is written to stdout. The host shows the
server's stderr in its MCP log (OpenCode
opencode.json→ the MCP panel, Claude Codeclaude --mcp-debug, VS Code MCP: List Servers → Show Output, Cursor's MCP settings page);-log-level debugin theargslogs every tool call there. - The host cannot find the binary. A block from
-print-confignames the absolute path, so this means the binary moved; print the block again, or use-print-config-command sysml-mcpand make sure the host'sPATHhas it (a GUI host does not read a shell's profile). - "unexpected argument": the server takes flags only; a host that passes a file or a
directory should pass
-root DIRinstead. - A tool is missing.
-toolsleft its group out, or the host caches the catalogue: restart the server from the host after changing its arguments. - A tool answered a budget error. Raise the
OPENSYSML_MAX_*variable it names in the host's environment for the server (theenvkey most hosts accept besidecommand), not in your shell.