Skip to content

Commit 316aa89

Browse files
committed
feat(mcp): SYSMLEGRAPH_SUBSCRIBER; docs: INSTALL two-repo, worker exit examples
- Omit indexDbGraph/clean_index when SYSMLEGRAPH_SUBSCRIBER=1 - INSTALL: TOC, TCP server vs repo, env tables, exit code examples - README, PLAN, TOOLS, MCP_SERVER_FOR_CURSOR, WORKER_CONTRACT updates - package.json description tweak Made-with: Cursor
1 parent a57f106 commit 316aa89

8 files changed

Lines changed: 221 additions & 44 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# sysmledgraph
22

3-
Path-only SysML indexer: builds a knowledge graph from `.sysml` files and exposes it via **MCP** (for Cursor AI) and **CLI**. The LSP used for indexing lives in **`lsp/`** and is used only by this repo.
3+
**Why:** When SysML v2 is the **single source of truth** for structure, connections, and requirements—especially in **AI-assisted (“vibe”) workflows**—you still need a way to treat the model like **navigable data**: “what uses this?”, “what’s connected?”, “give me context for the assistant.” This tool is that layer for **paths of `.sysml` files**, the same way [**GitNexus**](https://github.com/abhigyanpatwari/GitNexus) turns a **code** tree into a queryable graph for agents—here the **symbols and relations** come from the **SysML language server** (and optional MCP fallback), not from parsing C/JavaScript.
4+
5+
**What:** A **path-only indexer** that builds a **Kuzu** knowledge graph and exposes it through a **CLI** and an **MCP server** (e.g. Cursor): index, query, context, impact, Cypher, map generation, and more. Intended as the **graph tool for a SysML Modelbase** (where you index); a **second workspace (codebase)** can **subscribe** to the **same DB** via the **long-lived TCP worker** and shared **`SYSMEDGRAPH_STORAGE_ROOT`**—see [docs/PLAN.md](docs/PLAN.md) (product positioning, Phase 7). A **long-lived TCP worker** (localhost) avoids Kuzu file-lock fights between processes. Indexing uses **`lsp/`** (or **`SYSMLLSP_SERVER_PATH`**) to resolve symbols; see [Install](#install).
46

57
- **npm:** [sysmledgraph](https://www.npmjs.com/package/sysmledgraph)`npm install sysmledgraph`
68
- **Repo:** [github.com/chouswei/codebase-sysmledgraph](https://github.com/chouswei/codebase-sysmledgraph)
9+
- **Background:** [Integrating SysML as the single source of truth in vibe-coding](https://www.linkedin.com/pulse/integrating-sysml-single-source-truth-vibe-coding-szu-wei-chou-owhtc/) (LinkedIn article)
710

811
## Contents
912

@@ -76,6 +79,7 @@ Or one step from repo root: `npm run index-and-map` (optional path argument).
7679
| **`SYSMEDGRAPH_STORAGE_ROOT`** | Graph storage directory (default `~/.sysmledgraph`). Merged DB: `<root>/db/graph.kuzu`. |
7780
| **`SYSMLEGRAPH_WORKER_URL`** | Long-lived worker endpoint, e.g. `127.0.0.1:PORT` (overrides reading `worker.port` from storage root). |
7881
| **`SYSMLEGRAPH_WORKER_STRICT`** | Set to **`1`** so graph clients **fail** if the TCP worker is unreachable (no silent in-process fallback). |
82+
| **`SYSMLEGRAPH_SUBSCRIBER`** | Set to **`1`** in a **Subscriber** (codebase) MCP config so **`indexDbGraph`** / **`clean_index`** are **not registered** (Publisher still indexes). Does **not** enforce a single daemon—see **`worker.lock`** / **INSTALL.md**. |
7983
| **`SYSMLEGRAPH_WORKER_PORT`** | Daemon bind port; **`0`** = OS-assigned (default when unset). Used when running `worker:daemon` / `daemon.js` directly. |
8084
| **`SYSMLLSP_SERVER_PATH`** | Path to sysml-v2-lsp **`server.js`** if not using default `lsp/` or `node_modules` resolution. |
8185
| **`SYSMEDGRAPH_USE_MCP_SYMBOLS`** | Set to **`1`** to fall back to MCP `getSymbols` when LSP returns no symbols (indexing). |

docs/INSTALL.md

Lines changed: 129 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
# Install
22

3+
How to build sysmledgraph, run the SysML indexer, and optionally use a **long-lived TCP worker** so CLI and MCP share one Kuzu database without file-lock errors. If you split **models** and **application code** across two Git repos, use [Two Git repos](#two-git-repos-modelbase-and-codebase).
4+
5+
**Contents:** [Quick start](#quick-start) · [LSP](#lsp) · [Kuzu](#kuzu) · [Long-lived graph worker](#long-lived-graph-worker-optional) · [Worker CLI exit codes & examples](#worker-cli-exit-codes-and-examples) · [Two Git repos](#two-git-repos-modelbase-and-codebase)
6+
7+
---
8+
39
## Quick start
410

511
1. **Root:** `npm install` then `npm run build`. On **Windows**, if **`sysml-v2-lsp`** fails during install (its script uses shell syntax), use **`npm install --ignore-scripts`** then **`node node_modules/kuzu/install.js`**.
612
2. **LSP (for indexing):** The canonical LSP for this repo is in **`lsp/`**. Run **`npm run setup-lsp`** (uses `--ignore-scripts`) or **`cd lsp && npm install --ignore-scripts`**. No need to set **`SYSMLLSP_SERVER_PATH`** when using this default.
7-
3. **Index:** `npx sysmledgraph analyze <path>` or use the MCP tool **indexDbGraph** after enabling the sysmledgraph MCP in Cursor (see docs/MCP_SERVER_FOR_CURSOR.md).
13+
3. **Index:** `npx sysmledgraph analyze <path>`, or enable the sysmledgraph MCP in Cursor and use **indexDbGraph** (see [MCP_SERVER_FOR_CURSOR.md](MCP_SERVER_FOR_CURSOR.md)).
14+
4. **Modelbase + Codebase (two Git repos):** If models and app code live in different repos but should share one graph, read [Two Git repos](#two-git-repos-modelbase-and-codebase) below.
15+
16+
**Using CLI and MCP together on the same graph:** Start **`npx sysmledgraph worker start --detach`** once (see [Long-lived graph worker](#long-lived-graph-worker-optional)) so both use TCP instead of opening **`graph.kuzu`** twice.
17+
18+
---
819

920
## LSP
1021

11-
- **Source:** [sysml-v2-lsp](https://www.npmjs.com/package/sysml-v2-lsp) (npm). The only copy used by sysmledgraph for indexing is in **`lsp/`** (see docs/PLAN_INDEPENDENT_LSP.md).
22+
- **Source:** [sysml-v2-lsp](https://www.npmjs.com/package/sysml-v2-lsp) (npm). The only copy used by sysmledgraph for indexing is in **`lsp/`** (see [PLAN_INDEPENDENT_LSP.md](PLAN_INDEPENDENT_LSP.md)).
1223
- **Setup:** `npm run setup-lsp` installs it in `lsp/` with `--ignore-scripts` (required on **Windows** for **sysml-v2-lsp ≥ 0.8**). Manual: `cd lsp && npm install --ignore-scripts`.
1324

1425
### Updating language support (“grammar” via the server)
@@ -27,22 +38,128 @@
2738

2839
**Coloring / TextMate “grammar”** in Cursor or VS Code usually comes from a **SysML or SysML v2 extension** in the marketplace, not from sysmledgraph. To change that: install or update the relevant extension, or adjust **`files.associations`** / theme settings in the editor — see the extension’s docs.
2940

41+
---
42+
3043
## Kuzu
3144

32-
If `npm install` fails or you use `--ignore-scripts`, build Kuzu manually: `node node_modules/kuzu/install.js`. See docs/INSTALL_NOTES.md if present.
45+
If `npm install` fails or you use `--ignore-scripts`, build Kuzu manually: `node node_modules/kuzu/install.js`. If **`docs/INSTALL_NOTES.md`** exists in your checkout, see it for platform notes.
46+
47+
---
3348

3449
## Long-lived graph worker (optional)
3550

36-
To avoid **Kuzu file-lock** conflicts when using the **CLI** and **MCP server** at the same time on the same storage root, run a single TCP worker that owns the database:
51+
To avoid **Kuzu file-lock** conflicts when using the **CLI** and **MCP server** at the same time on the same storage root, run **one** TCP worker process that owns **`db/graph.kuzu`**. CLI and MCP then talk to it over **localhost** (see [DESIGN_LONG_LIVED_WORKER.md](DESIGN_LONG_LIVED_WORKER.md), [WORKER_CONTRACT.md](WORKER_CONTRACT.md)).
52+
53+
| Step | Command / setting |
54+
|------|-------------------|
55+
| **Start** | `npx sysmledgraph worker start --detach` (background) or `npx sysmledgraph worker start` (foreground). Writes **`worker.port`** under the storage root (default `~/.sysmledgraph`). |
56+
| **Same storage** | CLI (`--storage` / **`SYSMEDGRAPH_STORAGE_ROOT`**) and MCP **`env`** must use the **same** root. With **`worker.port`** or **`SYSMLEGRAPH_WORKER_URL`** (e.g. `http://127.0.0.1:9123`), clients use TCP instead of in-process Kuzu. |
57+
| **Stop** | `npx sysmledgraph worker stop` — graceful shutdown, then removes **`worker.port`**. |
58+
| **Status** | `npx sysmledgraph worker status` — exit **0** if the port responds. |
59+
| **Strict** | **`SYSMLEGRAPH_WORKER_STRICT=1`** — if a worker URL/port is configured but the daemon is unreachable, **fail** instead of falling back to in-process (avoids mixed mode). |
60+
61+
Fixed listen port (optional): **`SYSMLEGRAPH_WORKER_PORT`** (e.g. `9192`); otherwise the OS assigns a port and writes it to **`worker.port`** line 1.
62+
63+
**E2E smoke:** `npm run test:daemon` (uses `vitest.e2e.config.ts`; not part of default `npm test`).
64+
65+
**Export / map:** `npx sysmledgraph graph export` / `npx sysmledgraph graph map` use the same gateway as MCP. `npm run export-graph` / `npm run generate-map` call those CLI commands.
66+
67+
### Worker CLI: exit codes and examples
68+
69+
Use the same **`SYSMEDGRAPH_STORAGE_ROOT`** (or **`--storage`**) for every command below.
70+
71+
| Command | **0** (success) | **1** (failure / not ready) | **2** (already running) |
72+
|---------|-----------------|----------------------------|-------------------------|
73+
| **`worker start`** | Foreground: runs until shutdown. **`--detach`:** background start | Daemon not built, stale **`worker.port`** with PID still alive, other errors | **`worker.port`** exists and **TCP responds** — worker already up |
74+
| **`worker stop`** | Shutdown attempted; **`worker.port`** removed | No **`worker.port`** ||
75+
| **`worker status`** | TCP responds (stdout: `running 127.0.0.1:<port>`) | No port file, TCP down, or **stale** **`worker.port`** (stderr explains); invalid port file ||
76+
77+
**Examples (stderr/stdout):**
78+
79+
```text
80+
# Up — exit 0
81+
$ npx sysmledgraph worker status
82+
running 127.0.0.1:9123
83+
```
84+
85+
```text
86+
# Down — exit 1
87+
$ npx sysmledgraph worker status
88+
not running
89+
```
90+
91+
```text
92+
# Port file left over, process gone — exit 1 (clean up with worker stop or delete worker.port)
93+
$ npx sysmledgraph worker status
94+
not running (stale worker.port: TCP closed on port 9123; run worker stop or delete worker.port)
95+
```
96+
97+
```text
98+
# Second start while daemon is up — exit 2
99+
$ npx sysmledgraph worker start --detach
100+
Worker already running (worker.port + TCP responds).
101+
```
102+
103+
If you run **`dist/.../daemon.js`** directly and another worker holds the lock, the process prints an error and exits **2** when the message indicates *already running*, otherwise **1**. Details: [WORKER_CONTRACT.md](WORKER_CONTRACT.md), [DESIGN_LONG_LIVED_WORKER.md](DESIGN_LONG_LIVED_WORKER.md) §9.4.
104+
105+
### Which repo starts the TCP server?
106+
107+
The daemon is **not** tied to which Git repo you have open. It is tied to **`SYSMEDGRAPH_STORAGE_ROOT`** (and the **`sysmledgraph`** binary you invoke). You can run **`worker start`** from a terminal whose cwd is **either** repo, as long as the environment points at the **same** storage root.
108+
109+
In a **Publisher / Subscriber** setup ([below](#two-git-repos-modelbase-and-codebase)):
110+
111+
- **Start the worker once** where you operate the graph (conventionally **Modelbase / Publisher**).
112+
- **Codebase / Subscriber** does **not** need to start the TCP server; it sets **`SYSMLEGRAPH_WORKER_URL`** and **connects** to the existing process. Starting a **second** worker for the **same** root fails (**`worker.lock`** or **`worker start`** exit **2**).
113+
114+
---
115+
116+
### Two Git repos: Modelbase and Codebase
117+
118+
Models often live in a **Modelbase** repo; implementation in a **Codebase** repo. Git remotes differ, but **one graph** is keyed by a shared **`SYSMEDGRAPH_STORAGE_ROOT`** directory, not by repo name.
119+
120+
| Short name | Typical repo | Role in one sentence |
121+
|------------|--------------|----------------------|
122+
| **Publisher** | SysML / modelbase | Owns indexing and usually **`worker start`**; one process holds **`graph.kuzu`**. |
123+
| **Subscriber** | App, firmware, services | Same storage root + **`SYSMLEGRAPH_WORKER_URL`** (or readable **`worker.port`**); uses query / context / impact / … |
124+
125+
**Minimal env (same machine)**
126+
127+
| Role | Required | Typical extras |
128+
|------|----------|----------------|
129+
| **Publisher** | **`SYSMEDGRAPH_STORAGE_ROOT`** | After **`worker start`**: none for local clients; or set **`SYSMLEGRAPH_WORKER_URL`** if you do not rely on **`worker.port`**. |
130+
| **Subscriber** | **`SYSMEDGRAPH_STORAGE_ROOT`** (same path) + **`SYSMLEGRAPH_WORKER_URL`** | **`SYSMLEGRAPH_SUBSCRIBER=1`**, **`SYSMLEGRAPH_WORKER_STRICT=1`** |
131+
132+
#### Publisher (Modelbase)
133+
134+
- Set **`SYSMEDGRAPH_STORAGE_ROOT`** to the directory that will contain **`db/graph.kuzu`** (default `~/.sysmledgraph` or a project path such as **`.sysmledgraph-local`**).
135+
- Build the graph: **`npx sysmledgraph analyze <path>`** or MCP **indexDbGraph**.
136+
- For CLI + MCP without Kuzu lock fights: **`npx sysmledgraph worker start --detach`**. Port → **`worker.port`** line 1; optional fixed port: **`SYSMLEGRAPH_WORKER_PORT`**.
137+
138+
#### Subscriber (Codebase)
139+
140+
- Use the **same absolute** **`SYSMEDGRAPH_STORAGE_ROOT`** as the Publisher.
141+
- Set **`SYSMLEGRAPH_WORKER_URL`** to **`127.0.0.1:<port>`** (from the Publisher’s **`worker.port`**, or the port you fixed with **`SYSMLEGRAPH_WORKER_PORT`**). The **MCP process does not start** the TCP server; it only connects.
142+
- Optional: **`SYSMLEGRAPH_SUBSCRIBER=1`** in MCP **`env`****indexDbGraph** / **clean_index** are not registered (fewer accidents from the wrong workspace).
143+
- Optional: **`SYSMLEGRAPH_WORKER_STRICT=1`** — graph calls fail if the worker is down (no silent in-process DB).
144+
- Do **not** run **`worker start`** for that same root from Codebase unless the Publisher worker is **not** running and you are deliberately taking over the same storage root from a shell (still only **one** daemon).
145+
146+
**`mcp.json` examples:** [MCP_SERVER_FOR_CURSOR.md](MCP_SERVER_FOR_CURSOR.md).
147+
148+
#### Same PC, two Cursor workspaces
149+
150+
One absolute **`SYSMEDGRAPH_STORAGE_ROOT`** in **both** projects. **Modelbase:** index + **`worker start`**. **Codebase:** **`SYSMLEGRAPH_WORKER_URL`** only — no second daemon for that root.
151+
152+
#### Different PCs
153+
154+
The daemon listens on **localhost** only on the machine where it runs. From **Codebase**, typical options: **SSH port forward** from the **Modelbase** host’s worker port to local **`127.0.0.1`**, then **`SYSMLEGRAPH_WORKER_URL`** to that local port; or a **shared filesystem** for the storage root (harder). See [PLAN.md](PLAN.md) Phase 7.
155+
156+
#### Only one worker per storage root
37157

38-
1. **Start:** `npx sysmledgraph worker start --detach` (background) or `npx sysmledgraph worker start` (foreground). Writes **`worker.port`** under your storage root (default `~/.sysmledgraph`).
39-
2. **Same storage:** Point CLI (`--storage` / `SYSMEDGRAPH_STORAGE_ROOT`) and the MCP server env at the **same** root. With `worker.port` present (or **`SYSMLEGRAPH_WORKER_URL`** e.g. `http://127.0.0.1:9123`), CLI and MCP talk to the worker over **localhost TCP** instead of opening Kuzu in-process.
40-
3. **Stop:** `npx sysmledgraph worker stop` — graceful shutdown via TCP, then removes `worker.port`.
41-
4. **Status:** `npx sysmledgraph worker status` — exit `0` if the port responds.
42-
5. **Strict:** If the worker is configured but unreachable, set **`SYSMLEGRAPH_WORKER_STRICT=1`** to fail instead of falling back to in-process (avoids accidental mixed mode). See **docs/DESIGN_LONG_LIVED_WORKER.md**.
158+
What **actually** enforces a single daemon (hiding MCP tools does **not**):
43159

44-
Optional port: **`SYSMLEGRAPH_WORKER_PORT`** (e.g. `9192`); default is an OS-assigned port written to `worker.port`.
160+
- **`worker.lock`** — exclusive lock; a second process on the **same** root gets an error such as *Another graph worker is already running*.
161+
- **`worker start`** — if TCP already answers on **`worker.port`**, start exits **2** (already running).
45162

46-
E2E smoke (subprocess daemon): `npm run test:daemon` (uses `vitest.e2e.config.ts`; not part of default `npm test`).
163+
**`SYSMLEGRAPH_SUBSCRIBER=1`** only removes **index** / **clean** from the MCP tool list; it does not stop **`worker start`** from a terminal.
47164

48-
**Export / map (worker-aware):** `npx sysmledgraph graph export [file]` and `npx sysmledgraph graph map [file]` use the same gateway as MCP (TCP worker when configured). `npm run export-graph` / `npm run generate-map` call those CLI commands.
165+
For a full env var list, see [README.md](../README.md) (repository root).

docs/MCP_SERVER_FOR_CURSOR.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ This repo works as an **MCP server** so Cursor (and other MCP clients) can use t
66

77
- **Server name:** `sysmledgraph`
88
- **Transport:** stdio (Cursor spawns the process and talks over stdin/stdout)
9-
- **Tools:** indexDbGraph, list_indexed, clean_index, cypher, query, context, impact, rename, generate_map
9+
- **Tools (Publisher default):** indexDbGraph, list_indexed, clean_index, cypher, query, context, impact, rename, generate_map
10+
**Subscriber:** set **`SYSMLEGRAPH_SUBSCRIBER=1`** in MCP **`env`** so **indexDbGraph** and **clean_index** are **not** registered (see **docs/INSTALL.md** — single daemon is enforced by **worker.lock**, not by hiding tools).
1011
- **Resources:** `sysmledgraph://context`, `sysmledgraph://schema` (and per-path variants when paths are indexed)
1112

1213
Cursor AI can call these tools when you ask about your SysML model (e.g. “what uses Modelbase?”, “show me the graph map”, “list symbols in the deploy model”).
@@ -46,6 +47,16 @@ Create or edit **`.cursor/mcp.json`** in the project root:
4647
- **SYSMEDGRAPH_STORAGE_ROOT:** Default is `~/.sysmledgraph`. Set to a custom path if you use one for the CLI.
4748
- **SYSMLLSP_SERVER_PATH (optional):** Only if not using the default. When running from this repo, the LSP in **`lsp/`** is used automatically after **`npm run setup-lsp`**.
4849

50+
**Subscriber (codebase) — same graph as Modelbase:** Use the **same** **`SYSMEDGRAPH_STORAGE_ROOT`** as the Publisher, set **`SYSMLEGRAPH_WORKER_URL`** to the running daemon (see **`worker.port`**), and add **`SYSMLEGRAPH_SUBSCRIBER": "1"`** so the assistant does not see **indexDbGraph** / **clean_index**. Example **`env`** fragment:
51+
52+
```json
53+
"env": {
54+
"SYSMEDGRAPH_STORAGE_ROOT": "C:/Users/YOU/.sysmledgraph",
55+
"SYSMLEGRAPH_WORKER_URL": "127.0.0.1:9123",
56+
"SYSMLEGRAPH_SUBSCRIBER": "1"
57+
}
58+
```
59+
4960
**Option B — Absolute path to built server**
5061

5162
If the built server lives elsewhere (e.g. a global tools folder):

0 commit comments

Comments
 (0)