@qe-mcp/server-ena 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +2 -1
- package/README.md +14 -8
- package/package.json +1 -1
- package/server.js +61 -1
package/.gitlab-ci.yml
CHANGED
|
@@ -60,7 +60,8 @@ verify-tag-version:
|
|
|
60
60
|
script:
|
|
61
61
|
- |
|
|
62
62
|
TAG_VERSION="${CI_COMMIT_TAG#v}"
|
|
63
|
-
|
|
63
|
+
# Parse with sed — the template's alpine image has no node
|
|
64
|
+
PKG_VERSION=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' package.json | head -1)
|
|
64
65
|
if [ "${TAG_VERSION}" != "${PKG_VERSION}" ]; then
|
|
65
66
|
echo "-----------------------------------------------------------"
|
|
66
67
|
echo " Tag/package.json mismatch — release blocked"
|
package/README.md
CHANGED
|
@@ -30,6 +30,8 @@ Gives Claude a set of ENA tools that run **locally on your machine**. The WASM m
|
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
33
|
+
The package is published to the public npm registry — no registry configuration needed.
|
|
34
|
+
|
|
33
35
|
### Option A — npx (no install needed)
|
|
34
36
|
|
|
35
37
|
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac):
|
|
@@ -39,24 +41,17 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac):
|
|
|
39
41
|
"mcpServers": {
|
|
40
42
|
"ena": {
|
|
41
43
|
"command": "npx",
|
|
42
|
-
"args": ["@qe-mcp/server-ena"]
|
|
44
|
+
"args": ["--yes", "@qe-mcp/server-ena"]
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
```
|
|
47
49
|
|
|
48
|
-
Then add the registry to your `~/.npmrc` once:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
echo "@qe-mcp:registry=https://gitlab.com/api/v4/projects/22522458/packages/npm/" >> ~/.npmrc
|
|
52
|
-
```
|
|
53
|
-
|
|
54
50
|
Restart Claude Desktop.
|
|
55
51
|
|
|
56
52
|
### Option B — global install
|
|
57
53
|
|
|
58
54
|
```bash
|
|
59
|
-
echo "@qe-mcp:registry=https://gitlab.com/api/v4/projects/22522458/packages/npm/" >> ~/.npmrc
|
|
60
55
|
npm install -g @qe-mcp/server-ena
|
|
61
56
|
```
|
|
62
57
|
|
|
@@ -91,6 +86,17 @@ npm install
|
|
|
91
86
|
}
|
|
92
87
|
```
|
|
93
88
|
|
|
89
|
+
### Dev builds
|
|
90
|
+
|
|
91
|
+
Pre-release builds are published only to the QE GitLab registry with a `dev` tag.
|
|
92
|
+
To use one, add the scope mapping once and pin `@dev`:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
echo "@qe-mcp:registry=https://gitlab.com/api/v4/projects/22522458/packages/npm/" >> ~/.npmrc
|
|
96
|
+
echo "@qe-libs:registry=https://gitlab.com/api/v4/projects/22522458/packages/npm/" >> ~/.npmrc
|
|
97
|
+
npx --yes @qe-mcp/server-ena@dev
|
|
98
|
+
```
|
|
99
|
+
|
|
94
100
|
### Verify
|
|
95
101
|
|
|
96
102
|
In Claude Desktop, open a new conversation and ask:
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -1145,9 +1145,69 @@ const server = new Server(
|
|
|
1145
1145
|
{ capabilities: { tools: {} } }
|
|
1146
1146
|
);
|
|
1147
1147
|
|
|
1148
|
+
// ── rena-wasm API adapter ─────────────────────────────────────────────────────
|
|
1149
|
+
// rena-wasm ≥ the R-mirroring rewrite returns an R-style ENA set object
|
|
1150
|
+
// (lineWeights / points / rotation.nodes / model.unitLabels) instead of the
|
|
1151
|
+
// original flat object (networks / centroids / positions / unitLabels).
|
|
1152
|
+
// These shims expose the flat field names this server is written against, and
|
|
1153
|
+
// are idempotent no-ops on the older flat API.
|
|
1154
|
+
|
|
1155
|
+
function adaptModel(m) {
|
|
1156
|
+
if (!m || m.networks !== undefined || m.lineWeights === undefined) return m;
|
|
1157
|
+
return {
|
|
1158
|
+
networks: m.lineWeights, // sphere-normed adjacency (nUnits × nConn)
|
|
1159
|
+
centroids: m.points, // projected unit positions (nUnits × dims)
|
|
1160
|
+
positions: m.rotation?.nodes, // code node positions (nCodes × dims)
|
|
1161
|
+
connectionNames: m.connectionNames,
|
|
1162
|
+
unitLabels: m.model?.unitLabels ?? m.unitLabels,
|
|
1163
|
+
columnNames: m.rotation?.columnNames,
|
|
1164
|
+
eigenvalues: m.rotation?.eigenvalues,
|
|
1165
|
+
nUnits: m.nUnits,
|
|
1166
|
+
nConnections: m.nConnections,
|
|
1167
|
+
dims: m.dims,
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function adaptAccum(a) {
|
|
1172
|
+
if (!a || a.networks !== undefined || a.connectionCounts === undefined) return a;
|
|
1173
|
+
return { ...a, networks: a.connectionCounts };
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// A WASM abort (raised by libqe on a degenerate SVD / means rotation) leaves the
|
|
1177
|
+
// module instance unusable — every later call would also abort. Detect it,
|
|
1178
|
+
// discard the cached instance so the next call reloads a fresh one, and surface
|
|
1179
|
+
// an actionable message instead of the raw "Aborted(undefined)".
|
|
1180
|
+
function isWasmAbort(e) {
|
|
1181
|
+
return /abort|RuntimeError|out of bounds|memory access|table index/i.test(e?.message ?? '');
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const DEGENERATE_FIT_MSG =
|
|
1185
|
+
'ENA rotation could not be computed — the data is too sparse or degenerate. ' +
|
|
1186
|
+
'Common causes: fewer than 2 units have any code co-occurrences within the ' +
|
|
1187
|
+
'stanza window; only one code column; or all units have identical networks. ' +
|
|
1188
|
+
'Use ena_accumulate to inspect the raw co-occurrence networks, and check that ' +
|
|
1189
|
+
'you have 2+ code columns that actually co-occur and 2+ units with non-empty networks.';
|
|
1190
|
+
|
|
1148
1191
|
let enaPromise = null;
|
|
1149
1192
|
function getENA() {
|
|
1150
|
-
if (!enaPromise) enaPromise = loadENA()
|
|
1193
|
+
if (!enaPromise) enaPromise = loadENA().then(ena => {
|
|
1194
|
+
const guard = (fn, adapt) => (rows, opts) => {
|
|
1195
|
+
try {
|
|
1196
|
+
return adapt(fn.call(ena, rows, opts));
|
|
1197
|
+
} catch (e) {
|
|
1198
|
+
if (isWasmAbort(e)) {
|
|
1199
|
+
enaPromise = null; // force a fresh WASM instance next call
|
|
1200
|
+
throw new Error(DEGENERATE_FIT_MSG);
|
|
1201
|
+
}
|
|
1202
|
+
throw e;
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
return {
|
|
1206
|
+
...ena,
|
|
1207
|
+
fit: guard(ena.fit, adaptModel),
|
|
1208
|
+
accumulate: guard(ena.accumulate, adaptAccum),
|
|
1209
|
+
};
|
|
1210
|
+
});
|
|
1151
1211
|
return enaPromise;
|
|
1152
1212
|
}
|
|
1153
1213
|
|